/*
 * UAE - The Un*x Amiga Emulator
 *
 * routines to handle compressed file automatically
 *
 * (c) 1996 Samuel Devulder, Tim Gunn
 *     2002-2007 Toni Wilen
 */

#include "sysconfig.h"
#include "fsusage.h"
#include "zfile.h"
#include "zarchive.h"
#include "diskutil.h"
#include "fdi2raw.h"
#include "crc32.h"
#include "zlib.h"
#include "archivers/zip/unzip.h"
#include "archivers/dms/pfile.h"
#include "archivers/wrp/warp.h"
#include "gfxfilter.h"
#include "disk.h"
#include "fsdb.h"
#include "filesys.h"
#include "hardfile.h"
#include "posixemu.h"
#include "custom.h"
#include "win32.h"

#define RECURSIVE_ARCHIVES 1

static struct zfile* zlist = 0;

const TCHAR* uae_archive_extensions[] = { L"zip", L"rar", L"7z", L"lha", L"lzh", L"lzx", L"tar", nullptr };

#define MAX_CACHE_ENTRIES 10

struct zdisktrack
{
    void* data;
    int len;
};
struct zdiskimage
{
    int tracks;
    struct zdisktrack zdisktracks[2 * 84];
};
struct zcache
{
    TCHAR* name;
    struct zdiskimage* zd;
    void* data;
    int size;
    struct zcache* next;
    time_t tm;
};
static struct zcache* zcachedata;

static struct zcache* cache_get(const TCHAR* name)
{
    struct zcache* zc = zcachedata;
    while (zc)
    {
        if (!_tcscmp(name, zc->name))
        {
            zc->tm = time(nullptr);
            return zc;
        }
        zc = zc->next;
    }
    return nullptr;
}

static void zcache_flush()
{
}

static void zcache_free_data(struct zcache* zc)
{
    int i;
    if (zc->zd)
    {
        for (i = 0; i < zc->zd->tracks; i++)
        {
            free(zc->zd->zdisktracks[i].data);
        }
        free(zc->zd);
    }
    free(zc->data);
    free(zc->name);
}

static void zcache_free(struct zcache* zc)
{
    struct zcache* pl = nullptr;
    struct zcache* l = zcachedata;
    struct zcache* nxt = nullptr;

    while (l != zc)
    {
        if (l == 0)
            return;
        pl = l;
        l = l->next;
    }
    if (l)
        nxt = l->next;
    zcache_free_data(zc);
    if (l == 0)
        return;
    if (!pl)
        zcachedata = nxt;
    else
        pl->next = nxt;
}

static void zcache_close()
{
    struct zcache* zc = zcachedata;
    while (zc)
    {
        struct zcache* n = zc->next;
        zcache_free_data(zc);
        free(n);
        zc = n;
    }
}

static void zcache_check()
{
    int cnt = 0;
    struct zcache* zc = zcachedata, * last = nullptr;
    while (zc)
    {
        last = zc;
        zc = zc->next;
        cnt++;
    }
    Logger::Write(L"CACHE: %d\n", cnt);
    if (cnt >= MAX_CACHE_ENTRIES && last)
        zcache_free(last);
}

static struct zcache* zcache_put(const TCHAR* name, struct zdiskimage* data)
{
    struct zcache* zc;

    zcache_check();
    zc = xcalloc(struct zcache, 1);
    zc->next = zcachedata;
    zcachedata = zc;
    zc->zd = data;
    zc->name = _tcsdup(name);
    zc->tm = time(nullptr);
    return zc;
}

static void checkarchiveparent(struct zfile* z)
{
    //  unpack completely if opened in PEEK mode
    if (z->archiveparent)
        archive_unpackzfile(z);
}

static struct zfile* zfile_create(struct zfile* prev)
{
    struct zfile* z;

    z = xmalloc(struct zfile, 1);
    if (!z)
        return 0;
    memset(z, 0, sizeof *z);
    z->next = zlist;
    zlist = z;
    z->opencnt = 1;
    if (prev)
    {
        z->zfdmask = prev->zfdmask;
    }
    return z;
}

static void zfile_free(struct zfile* f)
{
    if (f->f)
        fclose(f->f);
    if (f->deleteafterclose)
    {
        _wunlink(f->name);
        Logger::Write(L"deleted temporary file '%s'\n", f->name);
    }
    free(f->name);
    free(f->data);
    free(f->mode);
    free(f->userdata);
    free(f);
}

void zfile_exit()
{
    struct zfile* l;
    while ((l = zlist))
    {
        zlist = l->next;
        zfile_free(l);
    }
}

void zfile_fclose(struct zfile* f)
{
    // Logger::Write (L"%p\n", f);
    if (!f)
        return;
    if (f->opencnt < 0)
    {
        Logger::Write(L"zfile: tried to free already closed filehandle!\n");
        return;
    }
    f->opencnt--;
    if (f->opencnt > 0)
        return;
    f->opencnt = -100;
    if (f->parent)
    {
        f->parent->opencnt--;
        if (f->parent->opencnt <= 0)
            zfile_fclose(f->parent);
    }
    if (f->archiveparent)
    {
        zfile_fclose(f->archiveparent);
        f->archiveparent = nullptr;
    }
    struct zfile* pl = nullptr;
    struct zfile* nxt = nullptr;
    struct zfile* l = zlist;
    while (l != f)
    {
        if (l == 0)
        {
            Logger::Write(L"zfile: tried to free already freed or nonexisting filehandle!\n");
            return;
        }
        pl = l;
        l = l->next;
    }
    if (l)
        nxt = l->next;
    zfile_free(f);
    if (l == 0)
        return;
    if (!pl)
        zlist = nxt;
    else
        pl->next = nxt;
}

static void removeext(TCHAR* s, TCHAR* ext)
{
    if (_tcslen(s) < _tcslen(ext))
        return;
    if (_tcsicmp(s + _tcslen(s) - _tcslen(ext), ext) == 0)
        s[_tcslen(s) - _tcslen(ext)] = 0;
}

static bool checkwrite(struct zfile* zf, int* retcode)
{
    if (zfile_needwrite(zf))
    {
        if (retcode)
            *retcode = -1;
        return true;
    }
    return false;
}

static byte exeheader[] = { 0x00, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00 };
static TCHAR* diskimages[] = { L"adf", L"adz", L"ipf", L"fdi", L"dms", L"wrp", L"dsq", 0 };

int zfile_gettype(struct zfile* z)
{
    byte buf[8];
    TCHAR* ext;

    if (!z || !z->name)
        return ZFILE_UNKNOWN;
    ext = _tcsrchr(z->name, '.');
    if (ext != nullptr)
    {
        int i;
        ext++;
        for (i = 0; diskimages[i]; i++)
        {
            if (_tcsicmp(ext, diskimages[i]) == 0)
                return ZFILE_DISKIMAGE;
        }
        if (_tcsicmp(ext, L"roz") == 0)
            return ZFILE_ROM;
        if (_tcsicmp(ext, L"uss") == 0)
            return ZFILE_STATEFILE;
        if (_tcsicmp(ext, L"rom") == 0)
            return ZFILE_ROM;
        if (_tcsicmp(ext, L"key") == 0)
            return ZFILE_KEY;
        if (_tcsicmp(ext, L"nvr") == 0)
            return ZFILE_NVR;
        if (_tcsicmp(ext, L"uae") == 0)
            return ZFILE_CONFIGURATION;
        if (_tcsicmp(ext, L"cue") == 0 || _tcsicmp(ext, L"iso") == 0 || _tcsicmp(ext, L"ccd") == 0 || _tcsicmp(ext, L"mds") == 0)
            return ZFILE_CDIMAGE;
    }
    memset(buf, 0, sizeof(buf));
    zfile_fread(buf, 8, 1, z);
    zfile_fseek(z, -8, SEEK_CUR);
    if (!memcmp(buf, exeheader, sizeof(buf)))
        return ZFILE_DISKIMAGE;
    if (!memcmp(buf, "RDSK", 4))
        return ZFILE_HDFRDB;
    if (!memcmp(buf, "DOS", 3))
    {
        if (z->size < 4 * 1024 * 1024)
            return ZFILE_DISKIMAGE;
        else
            return ZFILE_HDF;
    }
    if (ext != nullptr)
    {
        if (_tcsicmp(ext, L"hdf") == 0)
            return ZFILE_HDF;
        if (_tcsicmp(ext, L"hdz") == 0)
            return ZFILE_HDF;
    }
    return ZFILE_UNKNOWN;
}

#define VHD_DYNAMIC 3
#define VHD_FIXED 2

static __forceinline uint gl(byte* p)
{
    return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
}

static uint vhd_checksum(byte* p, int offset)
{
    int i;
    uint sum;

    sum = 0;
    for (i = 0; i < 512; i++)
    {
        if (offset >= 0 && i >= offset && i < offset + 4)
            continue;
        sum += p[i];
    }
    return ~sum;
}

struct zfile_vhd
{
    int vhd_type;
    ulong64 virtsize;
    uint vhd_bamoffset;
    uint vhd_blocksize;
    byte* vhd_header, * vhd_sectormap;
    ulong64 vhd_footerblock;
    uint vhd_bamsize;
    ulong64 vhd_sectormapblock;
    uint vhd_bitmapsize;
};

static ulong64 vhd_fread2(struct zfile* zf, void* dataptrv, ulong64 offset, ulong64 len)
{
    uint bamoffset;
    uint sectoroffset;
    ulong64 read;
    struct zfile* zp = zf->parent;
    struct zfile_vhd* zvhd = (struct zfile_vhd*)zf->userdata;
    byte* dataptr = (byte*)dataptrv;

    // Logger::Write (L"%08x %08x\n", (uint)offset, (uint)len);
    read = 0;
    if (offset & 511)
        return read;
    if (len & 511)
        return read;
    while (len > 0)
    {
        bamoffset = (offset / zvhd->vhd_blocksize) * 4 + zvhd->vhd_bamoffset;
        sectoroffset = gl(zvhd->vhd_header + bamoffset);
        if (sectoroffset == 0xffffffff)
        {
            memset(dataptr, 0, 512);
            read += 512;
        }
        else
        {
            int bitmapoffsetbits;
            int bitmapoffsetbytes;
            int sectormapblock;

            bitmapoffsetbits = (offset / 512) % (zvhd->vhd_blocksize / 512);
            bitmapoffsetbytes = bitmapoffsetbits / 8;
            sectormapblock = sectoroffset * 512 + (bitmapoffsetbytes & ~511);
            if (zvhd->vhd_sectormapblock != sectormapblock)
            {
                //  read sector bitmap
                // Logger::Write (L"BM %08x\n", sectormapblock);
                zfile_fseek(zp, sectormapblock, SEEK_SET);
                if (zfile_fread(zvhd->vhd_sectormap, 1, 512, zp) != 512)
                    return read;
                zvhd->vhd_sectormapblock = sectormapblock;
            }
            //  block allocated in bitmap?
            if (zvhd->vhd_sectormap[bitmapoffsetbytes & 511] & (1 << (7 - (bitmapoffsetbits & 7))))
            {
                //  read data block
                int block = sectoroffset * 512 + zvhd->vhd_bitmapsize + bitmapoffsetbits * 512;
                // Logger::Write (L"DB %08x\n", block);
                zfile_fseek(zp, block, SEEK_SET);
                if (zfile_fread(dataptr, 1, 512, zp) != 512)
                    return read;
            }
            else
            {
                memset(dataptr, 0, 512);
            }
            read += 512;
        }
        len -= 512;
        dataptr += 512;
        offset += 512;
    }
    return read;
}
static long64 vhd_fread(void* data, ulong64 l1, ulong64 l2, struct zfile* zf)
{
    ulong64 size = l1 * l2;
    ulong64 out = 0;
    int len = 0;

    if (!l1 || !l2)
        return 0;
    if ((zf->seek & 511) || (size & 511))
    {
        byte tmp[512];

        if (zf->seek & 511)
        {
            int s;
            s = 512 - (zf->seek & 511);
            vhd_fread2(zf, tmp, zf->seek & ~511, 512);
            memcpy((byte*)data + len, tmp + 512 - s, s);
            len += s;
            out += s;
            zf->seek += s;
        }
        while (size > 0)
        {
            int s = size > 512 ? 512 : size;
            vhd_fread2(zf, tmp, zf->seek, 512);
            memcpy((byte*)data + len, tmp, s);
            zf->seek += s;
            size -= s;
            out += s;
        }
    }
    else
    {
        out = vhd_fread2(zf, data, zf->seek, size);
        zf->seek += out;
        out /= l1;
    }
    return out;
}

static struct zfile* vhd(struct zfile* z)
{
    byte tmp[512], tmp2[512];
    uint v;
    struct zfile_vhd* zvhd;
    ulong64 fsize;

    zvhd = xcalloc(struct zfile_vhd, 1);
    zfile_fseek(z, 0, SEEK_END);
    fsize = zfile_ftell(z);
    zfile_fseek(z, 0, SEEK_SET);
    if (zfile_fread(tmp, 1, 512, z) != 512)
        goto nonvhd;
    v = gl(tmp + 8);  // features
    if ((v & 3) != 2)
        goto nonvhd;
    v = gl(tmp + 8 + 4);  // version
    if ((v >> 16) != 1)
        goto nonvhd;
    zvhd->vhd_type = gl(tmp + 8 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8 + 8 + 4);
    if (zvhd->vhd_type != VHD_FIXED && zvhd->vhd_type != VHD_DYNAMIC)
        goto nonvhd;
    v = gl(tmp + 8 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 4);
    if (v == 0)
        goto nonvhd;
    if (vhd_checksum(tmp, 8 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 4) != v)
        goto nonvhd;
    zfile_fseek(z, fsize - sizeof tmp2, SEEK_SET);
    if (zfile_fread(tmp2, 1, 512, z) != 512)
        goto end;
    if (memcmp(tmp, tmp2, sizeof tmp))
        goto nonvhd;
    zvhd->vhd_footerblock = fsize - 512;
    zvhd->virtsize = (ulong64)(gl(tmp + 8 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8)) << 32;
    zvhd->virtsize |= gl(tmp + 8 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8 + 4);
    if (zvhd->vhd_type == VHD_DYNAMIC)
    {
        uint size;
        zvhd->vhd_bamoffset = gl(tmp + 8 + 4 + 4 + 4);
        if (zvhd->vhd_bamoffset == 0 || zvhd->vhd_bamoffset >= fsize)
            goto end;
        zfile_fseek(z, zvhd->vhd_bamoffset, SEEK_SET);
        if (zfile_fread(tmp, 1, 512, z) != 512)
            goto end;
        v = gl(tmp + 8 + 8 + 8 + 4 + 4 + 4);
        if (vhd_checksum(tmp, 8 + 8 + 8 + 4 + 4 + 4) != v)
            goto end;
        v = gl(tmp + 8 + 8 + 8);
        if ((v >> 16) != 1)
            goto end;
        zvhd->vhd_blocksize = gl(tmp + 8 + 8 + 8 + 4 + 4);
        zvhd->vhd_bamoffset = gl(tmp + 8 + 8 + 4);
        zvhd->vhd_bamsize = (((zvhd->virtsize + zvhd->vhd_blocksize - 1) / zvhd->vhd_blocksize) * 4 + 511) & ~511;
        size = zvhd->vhd_bamoffset + zvhd->vhd_bamsize;
        zvhd->vhd_header = xmalloc(byte, size);
        zfile_fseek(z, 0, SEEK_SET);
        if (zfile_fread(zvhd->vhd_header, 1, size, z) != size)
            goto end;
        zvhd->vhd_sectormap = xmalloc(byte, 512);
        zvhd->vhd_sectormapblock = -1;
        zvhd->vhd_bitmapsize = ((zvhd->vhd_blocksize / (8 * 512)) + 511) & ~511;
    }
    z = zfile_fopen_parent(z, nullptr, 0, zvhd->virtsize);
    z->useparent = 0;
    z->dataseek = 1;
    z->userdata = zvhd;
    z->zfileread = vhd_fread;
    Logger::Write(L"%s is VHD %s image, virtual size=%dK\n",
        zfile_getname(z),
        zvhd->vhd_type == 2 ? L"fixed" : L"dynamic",
        zvhd->virtsize / 1024);
    return z;
nonvhd:
end:
    return z;
}

static struct zfile* zfile_gunzip(struct zfile* z, int* retcode)
{
    byte header[2 + 1 + 1 + 4 + 1 + 1];
    z_stream zs;
    int i, size, ret = 0, first;
    byte flags;
    long64 offset;
    TCHAR name[MAX_DPATH];
    byte buffer[8192];
    struct zfile* z2;
    byte b;

    if (checkwrite(z, retcode))
        return nullptr;
    _tcscpy(name, z->name);
    memset(&zs, 0, sizeof(zs));
    memset(header, 0, sizeof(header));
    zfile_fread(header, sizeof(header), 1, z);
    flags = header[3];
    if (header[0] != 0x1f && header[1] != 0x8b)
        return nullptr;
    if (flags & 2) /* multipart not supported */
        return nullptr;
    if (flags & 32) /* encryption not supported */
        return nullptr;
    if (flags & 4)   /* skip extra field */
    {
        zfile_fread(&b, 1, 1, z);
        size = b;
        zfile_fread(&b, 1, 1, z);
        size |= b << 8;
        zfile_fseek(z, size + 2, SEEK_CUR);
    }
    if (flags & 8)   /* get original file name */
    {
        char aname[MAX_DPATH];
        i = 0;
        do
        {
            zfile_fread(aname + i, 1, 1, z);
        }
        while (i < MAX_DPATH - 1 && aname[i++]);
        aname[i] = 0;
        Unicode::au_copy(name, MAX_DPATH, aname);
    }
    if (flags & 16)   /* skip comment */
    {
        i = 0;
        do
        {
            b = 0;
            zfile_fread(&b, 1, 1, z);
        }
        while (b);
    }
    removeext(name, L".gz");
    offset = zfile_ftell(z);
    zfile_fseek(z, -4, SEEK_END);
    zfile_fread(&b, 1, 1, z);
    size = b;
    zfile_fread(&b, 1, 1, z);
    size |= b << 8;
    zfile_fread(&b, 1, 1, z);
    size |= b << 16;
    zfile_fread(&b, 1, 1, z);
    size |= b << 24;
    if (size < 8 || size > 256 * 1024 * 1024) /* safety check */
        return nullptr;
    zfile_fseek(z, offset, SEEK_SET);
    z2 = zfile_fopen_empty(z, name, size);
    if (!z2)
        return nullptr;
    zs.next_out = z2->data;
    zs.avail_out = size;
    first = 1;
    do
    {
        zs.next_in = buffer;
        zs.avail_in = zfile_fread(buffer, 1, sizeof(buffer), z);
        if (first)
        {
            if (inflateInit2_(&zs, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream)) != Z_OK)
                break;
            first = 0;
        }
        ret = inflate(&zs, 0);
    }
    while (ret == Z_OK);
    inflateEnd(&zs);
    if (ret != Z_STREAM_END || first != 0)
    {
        zfile_fclose(z2);
        return nullptr;
    }
    zfile_fclose(z);
    return z2;
}
struct zfile* zfile_gunzip(struct zfile* z)
{
    return zfile_gunzip(z, nullptr);
}

static void truncate880k(struct zfile* z)
{
    int i;
    byte* b;

    if (z == nullptr || z->data == nullptr)
        return;
    if (z->size < 880 * 512 * 2)
    {
        int size = 880 * 512 * 2 - z->size;
        b = xcalloc(byte, size);
        zfile_fwrite(b, size, 1, z);
        free(b);
        return;
    }
    for (i = 880 * 512 * 2; i < z->size; i++)
    {
        if (z->data[i])
            return;
    }
    z->size = 880 * 512 * 2;
}

static struct zfile* extadf(struct zfile* z, int index, int* retcode)
{
    int i, r;
    struct zfile* zo;
    ushort* mfm;
    ushort* amigamfmbuffer;
    byte writebuffer_ok[32], * outbuf;
    int tracks, len, offs, pos;
    byte buffer[2 + 2 + 4 + 4];
    int outsize;
    TCHAR newname[MAX_DPATH];
    TCHAR* ext;
    int cantrunc = 0;
    int done = 0;

    if (index > 1)
        return nullptr;

    mfm = xcalloc(ushort, 32000 / 2);
    amigamfmbuffer = xcalloc(ushort, 32000 / 2);
    outbuf = xcalloc(byte, 16384);

    zfile_fread(buffer, 1, 8, z);
    zfile_fread(buffer, 1, 4, z);
    tracks = buffer[2] * 256 + buffer[3];
    offs = 8 + 2 + 2 + tracks * (2 + 2 + 4 + 4);

    _tcscpy(newname, zfile_getname(z));
    ext = _tcsrchr(newname, '.');
    if (ext)
    {
        _tcscpy(newname + _tcslen(newname) - _tcslen(ext), L".std.adf");
    }
    else
    {
        _tcscat(newname, L".std.adf");
    }
    if (index > 0)
        _tcscpy(newname + _tcslen(newname) - 4, L".ima");

    zo = zfile_fopen_empty(z, newname, 0);
    if (!zo)
        goto end;

    if (retcode)
        *retcode = 1;
    pos = 12;
    outsize = 0;
    for (i = 0; i < tracks; i++)
    {
        int type, bitlen;

        zfile_fseek(z, pos, SEEK_SET);
        zfile_fread(buffer, 2 + 2 + 4 + 4, 1, z);
        pos = zfile_ftell(z);
        type = buffer[2] * 256 + buffer[3];
        len = buffer[5] * 65536 + buffer[6] * 256 + buffer[7];
        bitlen = buffer[9] * 65536 + buffer[10] * 256 + buffer[11];

        zfile_fseek(z, offs, SEEK_SET);
        if (type == 1)
        {
            zfile_fread(mfm, len, 1, z);
            memset(writebuffer_ok, 0, sizeof writebuffer_ok);
            memset(outbuf, 0, 16384);
            if (index == 0)
            {
                r = isamigatrack(amigamfmbuffer, (byte*)mfm, len, outbuf, writebuffer_ok, i, &outsize);
                if (r < 0 && i == 0)
                {
                    zfile_seterror(L"'%s' is not AmigaDOS formatted", zo->name);
                    goto end;
                }
                if (i == 0)
                    done = 1;
            }
            else
            {
                r = ispctrack(amigamfmbuffer, (byte*)mfm, len, outbuf, writebuffer_ok, i, &outsize);
                if (r < 0 && i == 0)
                {
                    zfile_seterror(L"'%s' is not PC formatted", zo->name);
                    goto end;
                }
                if (i == 0)
                    done = 1;
            }
        }
        else
        {
            outsize = 512 * 11;
            if (bitlen / 8 > 18000)
                outsize *= 2;
            zfile_fread(outbuf, outsize, 1, z);
            cantrunc = 1;
            if (index == 0)
                done = 1;
        }
        zfile_fwrite(outbuf, outsize, 1, zo);

        offs += len;
    }
    if (done == 0)
        goto end;
    zfile_fclose(z);
    free(mfm);
    free(amigamfmbuffer);
    if (cantrunc)
        truncate880k(zo);
    return zo;
end:
    zfile_fclose(zo);
    free(mfm);
    free(amigamfmbuffer);
    return nullptr;
}

#include "fdi2raw.h"
static struct zfile* fdi(struct zfile* z, int index, int* retcode)
{
    int i, j, r;
    struct zfile* zo;
    TCHAR* orgname = zfile_getname(z);
    TCHAR* ext = _tcsrchr(orgname, '.');
    TCHAR newname[MAX_DPATH];
    ushort* amigamfmbuffer;
    byte writebuffer_ok[32], * outbuf;
    int tracks, len, outsize;
    FDI* fdi;
    int startpos = 0;
    byte tmp[12];
    struct zcache* zc;

    if (checkwrite(z, retcode))
        return nullptr;
    if (index > 2)
        return nullptr;

    zc = cache_get(z->name);
    if (!zc)
    {
        ushort* mfm;
        struct zdiskimage* zd;
        fdi = fdi2raw_header(z);
        if (!fdi)
            return nullptr;
        mfm = xcalloc(ushort, 32000 / 2);
        zd = xcalloc(struct zdiskimage, 1);
        tracks = fdi2raw_get_last_track(fdi);
        zd->tracks = tracks;
        for (i = 0; i < tracks; i++)
        {
            byte* buf, * p;
            fdi2raw_loadtrack(fdi, mfm, nullptr, i, &len, nullptr, nullptr, 1);
            len /= 8;
            buf = p = xmalloc(byte, len);
            for (j = 0; j < len / 2; j++)
            {
                ushort v = mfm[j];
                *p++ = v >> 8;
                *p++ = v;
            }
            zd->zdisktracks[i].data = buf;
            zd->zdisktracks[i].len = len;
        }
        fdi2raw_header_free(fdi);
        zc = zcache_put(z->name, zd);
    }

    amigamfmbuffer = xcalloc(ushort, 32000 / 2);
    outbuf = xcalloc(byte, 16384);
    tracks = zc->zd->tracks;
    if (ext)
    {
        _tcscpy(newname, orgname);
        _tcscpy(newname + _tcslen(newname) - _tcslen(ext), L".adf");
    }
    else
    {
        _tcscat(newname, L".adf");
    }
    if (index == 1)
        _tcscpy(newname + _tcslen(newname) - 4, L".ima");
    if (index == 2)
        _tcscpy(newname + _tcslen(newname) - 4, L".ext.adf");
    zo = zfile_fopen_empty(z, newname, 0);
    if (!zo)
        goto end;
    if (retcode)
        *retcode = 1;
    if (index > 1)
    {
        zfile_fwrite("UAE-1ADF", 8, 1, zo);
        tmp[0] = 0;
        tmp[1] = 0;             /* flags (reserved) */
        tmp[2] = 0;
        tmp[3] = tracks;             /* number of tracks */
        zfile_fwrite(tmp, 4, 1, zo);
        memset(tmp, 0, sizeof tmp);
        tmp[2] = 0;
        tmp[3] = 1;             /* track type */
        startpos = zfile_ftell(zo);
        for (i = 0; i < tracks; i++)
            zfile_fwrite(tmp, sizeof tmp, 1, zo);
    }
    outsize = 0;
    for (i = 0; i < tracks; i++)
    {
        byte* p = (byte*)zc->zd->zdisktracks[i].data;
        len = zc->zd->zdisktracks[i].len;
        memset(writebuffer_ok, 0, sizeof writebuffer_ok);
        memset(outbuf, 0, 16384);
        if (index == 0)
        {
            r = isamigatrack(amigamfmbuffer, p, len, outbuf, writebuffer_ok, i, &outsize);
            if (r < 0 && i == 0)
            {
                zfile_seterror(L"'%s' is not AmigaDOS formatted", orgname);
                goto end;
            }
            zfile_fwrite(outbuf, outsize, 1, zo);
        }
        else if (index == 1)
        {
            r = ispctrack(amigamfmbuffer, p, len, outbuf, writebuffer_ok, i, &outsize);
            if (r < 0 && i == 0)
            {
                zfile_seterror(L"'%s' is not PC formatted", orgname);
                goto end;
            }
            zfile_fwrite(outbuf, outsize, 1, zo);
        }
        else
        {
            int pos = zfile_ftell(zo);
            int maxlen = len > 12798 ? len : 12798;
            int lenb = len * 8;

            if (maxlen & 1)
                maxlen++;
            zfile_fseek(zo, startpos + i * 12 + 4, SEEK_SET);
            tmp[4] = 0;
            tmp[5] = 0;
            tmp[6] = maxlen >> 8;
            tmp[7] = maxlen;
            tmp[8] = lenb >> 24;
            tmp[9] = lenb >> 16;
            tmp[10] = lenb >> 8;
            tmp[11] = lenb;
            zfile_fwrite(tmp + 4, 2, 4, zo);
            zfile_fseek(zo, pos, SEEK_SET);
            zfile_fwrite(p, 1, len, zo);
            if (maxlen > len)
                zfile_fwrite(outbuf, 1, maxlen - len, zo);
        }
    }
    zfile_fclose(z);
    free(amigamfmbuffer);
    free(outbuf);
    if (index == 0)
        truncate880k(zo);
    return zo;
end:
    zfile_fclose(zo);
    free(amigamfmbuffer);
    free(outbuf);
    return nullptr;
}

#ifdef CAPS
    #include "caps/caps_win32.h"
static struct zfile* ipf(struct zfile* z, int index, int* retcode)
{
    int i, j, r;
    struct zfile* zo;
    TCHAR* orgname = zfile_getname(z);
    TCHAR* ext = _tcsrchr(orgname, '.');
    TCHAR newname[MAX_DPATH];
    ushort* amigamfmbuffer;
    byte writebuffer_ok[32];
    int tracks, len;
    int outsize;
    int startpos = 0;
    byte* outbuf;
    byte tmp[12];
    struct zcache* zc;

    if (checkwrite(z, retcode))
        return nullptr;

    if (index > 2)
        return nullptr;

    zc = cache_get(z->name);
    if (!zc)
    {
        ushort* mfm;
        struct zdiskimage* zd;
        if (!caps_loadimage(z, 0, &tracks))
            return nullptr;
        mfm = xcalloc(ushort, 32000 / 2);
        zd = xcalloc(struct zdiskimage, 1);
        zd->tracks = tracks;
        for (i = 0; i < tracks; i++)
        {
            byte* buf, * p;
            int mrev, gapo;
            caps_loadtrack(mfm, nullptr, 0, i, &len, &mrev, &gapo);
            // Logger::Write (L"%d: %d %d %d\n", i, mrev, gapo, len);
            len /= 8;
            buf = p = xmalloc(byte, len);
            for (j = 0; j < len / 2; j++)
            {
                ushort v = mfm[j];
                *p++ = v >> 8;
                *p++ = v;
            }
            zd->zdisktracks[i].data = buf;
            zd->zdisktracks[i].len = len;
        }
        caps_unloadimage(0);
        zc = zcache_put(z->name, zd);
    }

    outbuf = xcalloc(byte, 16384);
    amigamfmbuffer = xcalloc(ushort, 32000 / 2);
    if (ext)
    {
        _tcscpy(newname, orgname);
        _tcscpy(newname + _tcslen(newname) - _tcslen(ext), L".adf");
    }
    else
    {
        _tcscat(newname, L".adf");
    }
    if (index == 1)
        _tcscpy(newname + _tcslen(newname) - 4, L".ima");
    if (index == 2)
        _tcscpy(newname + _tcslen(newname) - 4, L".ext.adf");

    zo = zfile_fopen_empty(z, newname, 0);
    if (!zo)
        goto end;

    if (retcode)
        *retcode = 1;

    tracks = zc->zd->tracks;

    if (index > 1)
    {
        zfile_fwrite("UAE-1ADF", 8, 1, zo);
        tmp[0] = 0;
        tmp[1] = 0;             /* flags (reserved) */
        tmp[2] = 0;
        tmp[3] = tracks;             /* number of tracks */
        zfile_fwrite(tmp, 4, 1, zo);
        memset(tmp, 0, sizeof tmp);
        tmp[2] = 0;
        tmp[3] = 1;             /* track type */
        startpos = zfile_ftell(zo);
        for (i = 0; i < tracks; i++)
            zfile_fwrite(tmp, sizeof tmp, 1, zo);
    }

    outsize = 0;
    for (i = 0; i < tracks; i++)
    {
        byte* p = (byte*)zc->zd->zdisktracks[i].data;
        len = zc->zd->zdisktracks[i].len;
        memset(writebuffer_ok, 0, sizeof writebuffer_ok);
        memset(outbuf, 0, 16384);
        if (index == 0)
        {
            r = isamigatrack(amigamfmbuffer, p, len, outbuf, writebuffer_ok, i, &outsize);
            if (r < 0 && i == 0)
            {
                zfile_seterror(L"'%s' is not AmigaDOS formatted", orgname);
                goto end;
            }
            zfile_fwrite(outbuf, 1, outsize, zo);
        }
        else if (index == 1)
        {
            r = ispctrack(amigamfmbuffer, p, len, outbuf, writebuffer_ok, i, &outsize);
            if (r < 0 && i == 0)
            {
                zfile_seterror(L"'%s' is not PC formatted", orgname);
                goto end;
            }
            zfile_fwrite(outbuf, outsize, 1, zo);
        }
        else
        {
            int pos = zfile_ftell(zo);
            int maxlen = len > 12798 ? len : 12798;
            int lenb = len * 8;

            if (maxlen & 1)
                maxlen++;
            zfile_fseek(zo, startpos + i * 12 + 4, SEEK_SET);
            tmp[4] = 0;
            tmp[5] = 0;
            tmp[6] = maxlen >> 8;
            tmp[7] = maxlen;
            tmp[8] = lenb >> 24;
            tmp[9] = lenb >> 16;
            tmp[10] = lenb >> 8;
            tmp[11] = lenb;
            zfile_fwrite(tmp + 4, 2, 4, zo);
            zfile_fseek(zo, pos, SEEK_SET);
            zfile_fwrite(p, 1, len, zo);
            if (maxlen > len)
                zfile_fwrite(outbuf, 1, maxlen - len, zo);
        }
    }
    zfile_fclose(z);
    free(amigamfmbuffer);
    free(outbuf);
    if (index == 0)
        truncate880k(zo);
    return zo;
end:
    zfile_fclose(zo);
    free(amigamfmbuffer);
    free(outbuf);
    return nullptr;
}
#endif

static struct zfile* dsq(struct zfile* z, int lzx, int* retcode)
{
    struct zfile* zi = nullptr;
    struct zvolume* zv = nullptr;

    if (checkwrite(z, retcode))
        return nullptr;
    if (lzx)
    {
        zv = archive_directory_lzx(z);
        if (zv)
        {
            if (zv->root.child)
                zi = archive_access_lzx(zv->root.child);
        }
    }
    else
    {
        zi = z;
    }
    if (zi)
    {
        byte* buf = zfile_getdata(zi, 0, -1);
        if (!memcmp(buf, "PKD\x13", 4) || !memcmp(buf, "PKD\x11", 4))
        {
            TCHAR* fn;
            int sectors = buf[18];
            int heads = buf[15];
            int blocks = (buf[6] << 8) | buf[7];
            int blocksize = (buf[10] << 8) | buf[11];
            struct zfile* zo;
            int size = blocks * blocksize;
            int off = buf[3] == 0x13 ? 52 : 32;
            int i;

            if (size < 1760 * 512)
                size = 1760 * 512;

            if (zfile_getfilename(zi) && _tcslen(zfile_getfilename(zi)))
            {
                fn = xmalloc(TCHAR, (_tcslen(zfile_getfilename(zi)) + 5));
                _tcscpy(fn, zfile_getfilename(zi));
                _tcscat(fn, L".adf");
            }
            else
            {
                fn = _tcsdup(L"dsq.adf");
            }
            zo = zfile_fopen_empty(z, fn, size);
            free(fn);
            for (i = 0; i < blocks / (sectors / heads); i++)
            {
                zfile_fwrite(buf + off, sectors * blocksize / heads, 1, zo);
                off += sectors * (blocksize + 16) / heads;
            }
            zfile_fclose_archive(zv);
            zfile_fclose(z);
            free(buf);
            return zo;
        }
        free(buf);
    }
    if (lzx)
        zfile_fclose(zi);
    return z;
}

static struct zfile* wrp(struct zfile* z, int* retcode)
{
    if (zfile_needwrite(z))
    {
        if (retcode)
            *retcode = -1;
        return nullptr;
    }
    return unwarp(z);
}

#include "7z/xz.h"
#include "7z/lzmadec.h"
#include "7z/7zcrc.h"

static void* SzAlloc(void* p, size_t size)
{
    return xmalloc(byte, size);
}
static void SzFree(void* p, void* address)
{
    free(address);
}
#define XZ_OUT_SIZE 10000
#define XZ_IN_SIZE 10000
static struct zfile* xz(struct zfile* z, int* retcode)
{
    static bool iscrc;
    ISzAlloc allocImp;
    CXzUnpacker cx;
    ECoderStatus status;
    struct zfile* zo = nullptr;
    byte out[XZ_OUT_SIZE], in[XZ_IN_SIZE];

    if (checkwrite(z, retcode))
        return nullptr;
    memset(&cx, 0, sizeof cx);
    allocImp.Alloc = SzAlloc;
    allocImp.Free = SzFree;
    if (!iscrc)
        CrcGenerateTable();
    iscrc = true;
    if (XzUnpacker_Create(&cx, &allocImp) != SZ_OK)
        return nullptr;
    // int outwritten = 0;
    // int towrite = 0;
    bool first = true;
    for (;;)
    {
        int read = zfile_fread(in, 1, XZ_IN_SIZE, z);
        if (first)
        {
            if (!(in[0] == 0xfd && in[1] == 0x37 && in[2] == 0x7a && in[3] == 0x58 && in[4] == 0x5a && in[5] == 0))
                break;
        }
        first = false;
        if (read == 0)
            break;
        if (read < 0)
        {
            zfile_fclose(zo);
            zo = nullptr;
            break;
        }
        if (!zo)
            zo = zfile_fopen_empty(z, z->name);
        if (!zo)
            break;
        byte* inp = in;
        for (;;)
        {
            SizeT srclen = read;
            SizeT outlen = XZ_OUT_SIZE;
            if (XzUnpacker_Code(&cx, out, &outlen, inp, &srclen, LZMA_FINISH_ANY, &status) != SZ_OK)
            {
                zfile_fclose(zo);
                zo = nullptr;
                break;
            }
            if (status != CODER_STATUS_NEEDS_MORE_INPUT && status != CODER_STATUS_NOT_FINISHED)
            {
                zfile_fclose(zo);
                zo = nullptr;
                break;
            }
            zfile_fwrite(out, outlen, 1, zo);
            if (status == CODER_STATUS_FINISHED_WITH_MARK)
                break;
            if (srclen == read)
                break;
            inp += srclen;
            read -= srclen;
        }
    }
    XzUnpacker_Free(&cx);
    return zo;
}

static struct zfile* dms(struct zfile* z, int index, int* retcode)
{
    int ret;
    struct zfile* zo;
    TCHAR* orgname = zfile_getname(z);
    TCHAR* ext = _tcsrchr(orgname, '.');
    TCHAR newname[MAX_DPATH];
    static int recursive;
    int i;
    struct zfile* zextra[DMS_EXTRA_SIZE] = { 0 };

    if (checkwrite(z, retcode))
        return nullptr;
    if (recursive)
        return nullptr;
    if (ext)
    {
        _tcscpy(newname, orgname);
        _tcscpy(newname + _tcslen(newname) - _tcslen(ext), L".adf");
    }
    else
    {
        _tcscat(newname, L".adf");
    }

    zo = zfile_fopen_empty(z, newname, 1760 * 512);
    if (!zo)
        return nullptr;
    ret = DMS_Process_File(z, zo, CMD_UNPACK, OPT_VERBOSE, 0, 0, 0, zextra);
    if (ret == NO_PROBLEM || ret == DMS_FILE_END)
    {
        int off = zfile_ftell(zo);
        if (off >= 1760 * 512 / 3 && off <= 1760 * 512 * 3 / 4)   // possible split dms?
        {
            if (_tcslen(orgname) > 5)
            {
                TCHAR* s = orgname + _tcslen(orgname) - 5;
                if (!_tcsicmp(s, L"a.dms"))
                {
                    TCHAR* fn2 = _tcsdup(orgname);
                    struct zfile* z2;
                    fn2[_tcslen(fn2) - 5]++;
                    recursive++;
                    z2 = zfile_fopen(fn2, L"rb", z->zfdmask);
                    recursive--;
                    if (z2)
                    {
                        ret = DMS_Process_File(z2, zo, CMD_UNPACK, OPT_VERBOSE, 0, 0, 1, nullptr);
                        zfile_fclose(z2);
                    }
                    free(fn2);
                }
            }
        }
        zfile_fseek(zo, 0, SEEK_SET);
        if (index > 0)
        {
            zfile_fclose(zo);
            zo = nullptr;
            for (i = 0; i < DMS_EXTRA_SIZE && zextra[i]; i++)
                ;
            if (index > i)
                goto end;
            zo = zextra[index - 1];
            zextra[index - 1] = nullptr;
        }
        if (retcode)
            *retcode = 1;
        zfile_fclose(z);
        z = nullptr;
    }
    else
    {
        zfile_fclose(zo);
        zo = nullptr;
    }
end:
    for (i = 0; i < DMS_EXTRA_SIZE; i++)
        zfile_fclose(zextra[i]);
    return zo;
}

const TCHAR* uae_ignoreextensions[] =
{ L".gif", L".jpg", L".png", L".xml", L".pdf", L".txt", 0 };
const TCHAR* uae_diskimageextensions[] =
{ L".adf", L".adz", L".ipf", L".fdi", L".exe", L".dms", L".wrp", L".dsq", 0 };

int zfile_is_ignore_ext(const TCHAR* name)
{
    int i;
    const TCHAR* ext;

    ext = _tcsrchr(name, '.');
    if (!ext)
        return 0;
    for (i = 0; uae_ignoreextensions[i]; i++)
    {
        if (!_tcsicmp(uae_ignoreextensions[i], ext))
            return 1;
    }
    return 0;
}

int zfile_is_diskimage(const TCHAR* name)
{
    int i;

    const TCHAR* ext = _tcsrchr(name, '.');
    if (!ext)
        return 0;
    i = 0;
    while (uae_diskimageextensions[i])
    {
        if (!_tcsicmp(ext, uae_diskimageextensions[i]))
            return HISTORY_FLOPPY;
        i++;
    }
    if (!_tcsicmp(ext, L".cue"))
        return HISTORY_CD;
    return -1;
}

static const TCHAR* archive_extensions[] = {
    L"7z", L"rar", L"zip", L"lha", L"lzh", L"lzx",
    L"adf", L"adz", L"dsq", L"dms", L"ipf", L"fdi", L"wrp", L"ima",
    L"hdf", L"tar",
    nullptr
};
static const TCHAR* plugins_7z[] = { L"7z", L"rar", L"zip", L"lha", L"lzh", L"lzx", L"adf", L"dsq", L"hdf", L"tar", nullptr };
static const char* plugins_7z_x[] = { "7z", "Rar!", "MK", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
static const int plugins_7z_t[] = {
    ArchiveFormat7Zip, ArchiveFormatRAR, ArchiveFormatZIP, ArchiveFormatLHA, ArchiveFormatLHA, ArchiveFormatLZX,
    ArchiveFormatADF, ArchiveFormatADF, ArchiveFormatADF, ArchiveFormatTAR
};
static const int plugins_7z_m[] = {
    ZFD_ARCHIVE, ZFD_ARCHIVE, ZFD_ARCHIVE, ZFD_ARCHIVE, ZFD_ARCHIVE, ZFD_ARCHIVE,
    ZFD_ADF, ZFD_ADF, ZFD_ADF, ZFD_ARCHIVE
};

int iszip(struct zfile* z, int mask)
{
    TCHAR* name = z->name;
    TCHAR* ext = _tcsrchr(name, '.');
    byte header[32];
    int i;

    if (!ext)
        return 0;
    memset(header, 0, sizeof(header));
    zfile_fseek(z, 0, SEEK_SET);
    zfile_fread(header, sizeof(header), 1, z);
    zfile_fseek(z, 0, SEEK_SET);

    if (mask & ZFD_ARCHIVE)
    {
        if (!_tcsicmp(ext, L".zip") || !_tcsicmp(ext, L".rp9"))
        {
            if (header[0] == 'P' && header[1] == 'K')
                return ArchiveFormatZIP;
            return 0;
        }
    }
    if (mask & ZFD_ARCHIVE)
    {
        if (!_tcsicmp(ext, L".7z"))
        {
            if (header[0] == '7' && header[1] == 'z')
                return ArchiveFormat7Zip;
            return 0;
        }
        if (!_tcsicmp(ext, L".rar"))
        {
            if (header[0] == 'R' && header[1] == 'a' && header[2] == 'r' && header[3] == '!')
                return ArchiveFormatRAR;
            return 0;
        }
        if (!_tcsicmp(ext, L".lha") || !_tcsicmp(ext, L".lzh"))
        {
            if (header[2] == '-' && header[3] == 'l' && header[4] == 'h' && header[6] == '-')
                return ArchiveFormatLHA;
            return 0;
        }
        if (!_tcsicmp(ext, L".lzx"))
        {
            if (header[0] == 'L' && header[1] == 'Z' && header[2] == 'X')
                return ArchiveFormatLZX;
            return 0;
        }
    }
    if (mask & ZFD_ADF)
    {
        if (!_tcsicmp(ext, L".adf"))
        {
            if (header[0] == 'D' && header[1] == 'O' && header[2] == 'S' && (header[3] >= 0 && header[3] <= 7))
                return ArchiveFormatADF;
            if (isfat(header))
                return ArchiveFormatFAT;
            return 0;
        }
        if (!_tcsicmp(ext, L".ima"))
        {
            if (isfat(header))
                return ArchiveFormatFAT;
        }
    }
    if (mask & ZFD_HD)
    {
        if (!_tcsicmp(ext, L".hdf"))
        {
            if (header[0] == 'D' && header[1] == 'O' && header[2] == 'S' && (header[3] >= 0 && header[3] <= 7))
                return ArchiveFormatADF;
            if (header[0] == 'S' && header[1] == 'F' && header[2] == 'S')
                return ArchiveFormatADF;
            if (header[0] == 'R' && header[1] == 'D' && header[2] == 'S' && header[3] == 'K')
                return ArchiveFormatRDB;
            if (isfat(header))
                return ArchiveFormatFAT;
            return 0;
        }
    }
    #if defined (ARCHIVEACCESS)
    for (i = 0; plugins_7z_x[i]; i++)
    {
        if ((plugins_7z_m[i] & mask) && plugins_7z_x[i] && !_tcsicmp(ext + 1, plugins_7z[i]) &&
            !memcmp(header, plugins_7z_x[i], strlen(plugins_7z_x[i])))
            return plugins_7z_t[i];
    }
    #endif
    return 0;
}
int iszip(struct zfile* z)
{
    return iszip(z, ZFD_NORMAL);
}

struct zfile* zuncompress(struct znode* parent, struct zfile* z, int dodefault, int mask, int* retcode, int index)
{
    TCHAR* name = z->name;
    TCHAR* ext = nullptr;
    byte header[32];
    int i;

    if (retcode)
        *retcode = 0;
    if (!mask)
        return nullptr;
    if (name)
    {
        ext = _tcsrchr(name, '.');
        if (ext)
            ext++;
    }

    if (ext != nullptr)
    {
        if (mask & ZFD_ARCHIVE)
        {
            if (_tcsicmp(ext, L"7z") == 0)
                return archive_access_select(parent, z, ArchiveFormat7Zip, dodefault, retcode, index);
            if (_tcsicmp(ext, L"zip") == 0)
                return archive_access_select(parent, z, ArchiveFormatZIP, dodefault, retcode, index);
            if (_tcsicmp(ext, L"lha") == 0 || _tcsicmp(ext, L"lzh") == 0)
                return archive_access_select(parent, z, ArchiveFormatLHA, dodefault, retcode, index);
            if (_tcsicmp(ext, L"lzx") == 0)
                return archive_access_select(parent, z, ArchiveFormatLZX, dodefault, retcode, index);
            if (_tcsicmp(ext, L"rar") == 0)
                return archive_access_select(parent, z, ArchiveFormatRAR, dodefault, retcode, index);
            if (_tcsicmp(ext, L"tar") == 0)
                return archive_access_select(parent, z, ArchiveFormatTAR, dodefault, retcode, index);
        }
        if (mask & ZFD_UNPACK)
        {
            if (index == 0)
            {
                if (_tcsicmp(ext, L"gz") == 0)
                    return zfile_gunzip(z, retcode);
                if (_tcsicmp(ext, L"adz") == 0)
                    return zfile_gunzip(z, retcode);
                if (_tcsicmp(ext, L"roz") == 0)
                    return zfile_gunzip(z, retcode);
                if (_tcsicmp(ext, L"hdz") == 0)
                    return zfile_gunzip(z, retcode);
                if (_tcsicmp(ext, L"wrp") == 0)
                    return wrp(z, retcode);
                if (_tcsicmp(ext, L"xz") == 0)
                    return xz(z, retcode);
            }
            if (_tcsicmp(ext, L"dms") == 0)
                return dms(z, index, retcode);
        }
        if (mask & ZFD_RAWDISK)
        {
            #ifdef CAPS
            if (_tcsicmp(ext, L"ipf") == 0)
                return ipf(z, index, retcode);
            #endif
            if (_tcsicmp(ext, L"fdi") == 0)
                return fdi(z, index, retcode);
            if (mask & (ZFD_RAWDISK_PC | ZFD_RAWDISK_AMIGA))
                return nullptr;
        }
        #if defined (ARCHIVEACCESS)
        if (index == 0)
        {
            for (i = 0; plugins_7z_x[i]; i++)
            {
                if ((plugins_7z_t[i] & mask) && _tcsicmp(ext, plugins_7z[i]) == 0)
                    return archive_access_arcacc_select(z, plugins_7z_t[i], retcode);
            }
        }
        #endif
    }
    memset(header, 0, sizeof(header));
    zfile_fseek(z, 0, SEEK_SET);
    zfile_fread(header, sizeof(header), 1, z);
    zfile_fseek(z, 0, SEEK_SET);
    if (!memcmp(header, "conectix", 8))
    {
        if (index > 0)
            return nullptr;
        return vhd(z);
    }

    if (mask & ZFD_UNPACK)
    {
        if (index == 0)
        {
            if (header[0] == 0x1f && header[1] == 0x8b)
                return zfile_gunzip(z, retcode);
            if (header[0] == 'P' && header[1] == 'K' && header[2] == 'D')
                return dsq(z, 0, retcode);
            if (header[0] == 0xfd && header[1] == 0x37 && header[2] == 0x7a && header[3] == 0x58 && header[4] == 0x5a && header[5] == 0)
                return xz(z, retcode);
        }
        if (header[0] == 'D' && header[1] == 'M' && header[2] == 'S' && header[3] == '!')
            return dms(z, index, retcode);
    }
    if (mask & ZFD_RAWDISK)
    {
        #ifdef CAPS
        if (header[0] == 'C' && header[1] == 'A' && header[2] == 'P' && header[3] == 'S')
            return ipf(z, index, retcode);
        #endif
        if (!memcmp(header, "Formatte", 8))
            return fdi(z, index, retcode);
        if (!memcmp(header, "UAE-1ADF", 8))
            return extadf(z, index, retcode);
    }
    if (index > 0)
        return nullptr;
    if (mask & ZFD_ARCHIVE)
    {
        if (header[0] == 'P' && header[1] == 'K')
            return archive_access_select(parent, z, ArchiveFormatZIP, dodefault, retcode, index);
        if (header[0] == 'R' && header[1] == 'a' && header[2] == 'r' && header[3] == '!')
            return archive_access_select(parent, z, ArchiveFormatRAR, dodefault, retcode, index);
        if (header[0] == 'L' && header[1] == 'Z' && header[2] == 'X')
            return archive_access_select(parent, z, ArchiveFormatLZX, dodefault, retcode, index);
        if (header[2] == '-' && header[3] == 'l' && header[4] == 'h' && header[6] == '-')
            return archive_access_select(parent, z, ArchiveFormatLHA, dodefault, retcode, index);
    }
    if (mask & ZFD_ADF)
    {
        if (header[0] == 'D' && header[1] == 'O' && header[2] == 'S' && (header[3] >= 0 && header[3] <= 7))
            return archive_access_select(parent, z, ArchiveFormatADF, dodefault, retcode, index);
        if (header[0] == 'S' && header[1] == 'F' && header[2] == 'S')
            return archive_access_select(parent, z, ArchiveFormatADF, dodefault, retcode, index);
        if (isfat(header))
            return archive_access_select(parent, z, ArchiveFormatFAT, dodefault, retcode, index);
    }

    if (ext)
    {
        if (mask & ZFD_UNPACK)
        {
            if (_tcsicmp(ext, L"dsq") == 0)
                return dsq(z, 1, retcode);
        }
        if (mask & ZFD_ADF)
        {
            if (_tcsicmp(ext, L"adf") == 0 && !memcmp(header, "DOS", 3))
                return archive_access_select(parent, z, ArchiveFormatADF, dodefault, retcode, index);
        }
    }
    return nullptr;
}

#ifdef SINGLEFILE
extern byte singlefile_data[];

static struct zfile* zfile_opensinglefile(struct zfile* l)
{
    byte* p = singlefile_data;
    int size, offset;
    TCHAR tmp[256], * s;

    _tcscpy(tmp, l->name);
    s = tmp + _tcslen(tmp) - 1;
    while (*s != 0 && *s != '/' && *s != '\\')
        s--;
    if (s > tmp)
        s++;
    Logger::Write(L"loading from singlefile: '%s'\n", tmp);
    while (*p++)
        ;
    offset = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
    p += 4;
    for (;;)
    {
        size = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
        if (!size)
            break;
        if (!strcmpi(tmp, p + 4))
        {
            l->data = singlefile_data + offset;
            l->size = size;
            Logger::Write(L"found, size %d\n", size);
            return l;
        }
        offset += size;
        p += 4;
        p += _tcslen(p) + 1;
    }
    Logger::Write(L"not found\n");
    return 0;
}
#endif

static struct zfile* zfile_fopen_nozip(const TCHAR* name, const TCHAR* mode)
{
    struct zfile* l;
    FILE* f;

    if (*name == '\0')
        return nullptr;
    l = zfile_create(nullptr);
    l->name = _tcsdup(name);
    l->mode = _tcsdup(mode);
    f = _tfopen(name, mode);
    if (!f)
    {
        zfile_fclose(l);
        return 0;
    }
    l->f = f;
    return l;
}

static struct zfile* openzip(const TCHAR* pname)
{
    int i, j;
    TCHAR v;
    TCHAR name[MAX_DPATH];
    TCHAR zippath[MAX_DPATH];

    zippath[0] = 0;
    _tcscpy(name, pname);
    i = _tcslen(name) - 2;
    while (i > 0)
    {
        if (name[i] == '/' || name[i] == '\\' && i > 4)
        {
            v = name[i];
            name[i] = 0;
            for (j = 0; plugins_7z[j]; j++)
            {
                int len = _tcslen(plugins_7z[j]);
                if (name[i - len - 1] == '.' && !_tcsicmp(name + i - len, plugins_7z[j]))
                {
                    struct zfile* f = zfile_fopen_nozip(name, L"rb");
                    if (f)
                    {
                        f->zipname = _tcsdup(name + i + 1);
                        return f;
                    }
                    break;
                }
            }
            name[i] = v;
        }
        i--;
    }
    return 0;
}

static bool writeneeded(const TCHAR* mode)
{
    return _tcschr(mode, 'w') || _tcschr(mode, 'a') || _tcschr(mode, '+') || _tcschr(mode, 't');
}
bool zfile_needwrite(struct zfile* zf)
{
    if (!zf->mode)
        return false;
    return writeneeded(zf->mode);
}

static struct zfile* zfile_fopen_2(const TCHAR* name, const TCHAR* mode, int mask)
{
    struct zfile* l;
    FILE* f;

    if (*name == '\0')
        return nullptr;
    #ifdef SINGLEFILE
    if (zfile_opensinglefile(l))
        return l;
    #endif
    l = openzip(name);
    if (l)
    {
        if (writeneeded(mode))
        {
            zfile_fclose(l);
            return 0;
        }
        l->zfdmask = mask;
    }
    else
    {
        struct _stat64 st;
        l = zfile_create(nullptr);
        l->mode = _tcsdup(mode);
        l->name = _tcsdup(name);
        l->zfdmask = mask;
        if (!_tcsicmp(mode, L"r"))
        {
            f = my_opentext(l->name);
            l->textmode = 1;
        }
        else
        {
            f = _tfopen(l->name, mode);
        }
        if (!f)
        {
            zfile_fclose(l);
            return 0;
        }
        if (stat(l->name, &st) != -1)
            l->size = st.st_size;
        l->f = f;
    }
    return l;
}

#ifdef _WIN32
    #include "win32.h"

    #define AF L"%AMIGAFOREVERDATA%"

static void manglefilename(TCHAR* out, const TCHAR* in)
{
    int i;

    out[0] = 0;
    if (!_tcsncicmp(in, AF, _tcslen(AF)))
        _tcscpy(out, start_path_data);
    if ((in[0] == '/' || in[0] == '\\') || (_tcslen(in) > 3 && in[1] == ':' && in[2] == '\\'))
        out[0] = 0;
    _tcscat(out, in);
    for (i = 0; i < _tcslen(out); i++)
    {
        //  remove \\ or // in the middle of path
        if ((out[i] == '/' || out[i] == '\\') && (out[i + 1] == '/' || out[i + 1] == '\\') && i > 0)
        {
            memmove(out + i, out + i + 1, (_tcslen(out + i) + 1) * sizeof(TCHAR));
            i--;
            continue;
        }
    }
}
#else
static void manglefilename(TCHAR* out, const TCHAR* in)
{
    _tcscpy(out, in);
}
#endif

int zfile_zopen(const TCHAR* name, zfile_callback zc, void* user)
{
    struct zfile* l;
    int ztype;
    TCHAR path[MAX_DPATH];

    manglefilename(path, name);
    l = zfile_fopen_2(path, L"rb", ZFD_NORMAL);
    if (!l)
        return 0;
    ztype = iszip(l);
    if (ztype == 0)
        zc(l, user);
    else
        archive_access_scan(l, zc, user, ztype);
    zfile_fclose(l);
    return 1;
}

/*
 * fopen() for a compressed file
 */
static struct zfile* zfile_fopen_x(const TCHAR* name, const TCHAR* mode, int mask, int index)
{
    int cnt = 10;
    struct zfile* l, * l2;
    TCHAR path[MAX_DPATH];

    if (_tcslen(name) == 0)
        return nullptr;
    manglefilename(path, name);
    l = zfile_fopen_2(path, mode, mask);
    if (!l)
        return 0;
    l2 = nullptr;
    while (cnt-- > 0)
    {
        int rc;
        zfile_fseek(l, 0, SEEK_SET);
        l2 = zuncompress(nullptr, l, 0, mask, &rc, index);
        if (!l2)
        {
            if (rc < 0)
            {
                zfile_fclose(l);
                return nullptr;
            }
            zfile_fseek(l, 0, SEEK_SET);
            break;
        }
        else
        {
            if (l2->parent == l)
                l->opencnt--;
        }
        l = l2;
    }
    return l;
}

#ifdef _WIN32
static int isinternetfile(const TCHAR* name)
{
    if (!_tcsnicmp(name, L"http://", 7) || !_tcsnicmp(name, L"https://", 8))
        return 1;
    if (!_tcsnicmp(name, L"ftp://", 6))
        return -1;
    return 0;
}

// #include <wininet.h>

    #define INETBUFFERLEN 1000000

static struct zfile* zfile_fopen_internet(const TCHAR* name, const TCHAR* mode, int mask)
{
    static HINTERNET hi;
    HINTERNET i = nullptr;
    TCHAR tmp[MAX_DPATH];
    DWORD ierr = 0;
    DWORD outbuf = sizeof tmp / sizeof(TCHAR);
    byte* data = 0;
    int bufferlen = INETBUFFERLEN;
    int datalen;
    DWORD didread;
    struct zfile* zf = nullptr;

    if (_tcschr(mode, 'w') || _tcschr(mode, 'a'))
        return nullptr;
    tmp[0] = 0;
    if (!hi)
    {
        hi = InternetOpen(WINUAEAPPNAME, INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, nullptr, nullptr, 0);
        if (hi == nullptr)
        {
            Logger::Write(L"InternetOpen() failed, %d\n", GetLastError());
            return nullptr;
        }
    }
    i = InternetOpenUrl(hi, name, nullptr, 0, INTERNET_FLAG_NO_COOKIES, 0);
    if (i == nullptr)
    {
        DWORD err = GetLastError();
        if (err == ERROR_INTERNET_EXTENDED_ERROR)
            InternetGetLastResponseInfo(&ierr, tmp, &outbuf);
        Logger::Write(L"InternetOpenUrl(%s) failed %d (%d,%s)\n", name, err, ierr, tmp);
        goto end;
    }

    if (isinternetfile(name) > 0)
    {
        DWORD statuscode;
        DWORD hindex = 0;
        DWORD size = sizeof statuscode;
        if (!HttpQueryInfo(i, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statuscode, &size, &hindex))
        {
            DWORD err = GetLastError();
            Logger::Write(L"HttpQueryInfo(%s) failed %d\n", name, err);
            goto end;
        }
        if (statuscode != 200)
        {
            Logger::Write(L"HttpQueryInfo(%s)=%d\n", name, statuscode);
            goto end;
        }
    }

    if (mask & ZFD_CHECKONLY)
    {
        zf = zfile_create(nullptr);
        goto end;
    }

    datalen = 0;
    data = xmalloc(byte, bufferlen);
    for (;;)
    {
        if (!InternetReadFile(i, data + datalen, INETBUFFERLEN, &didread))
        {
            DWORD err = GetLastError();
            if (err == ERROR_INTERNET_EXTENDED_ERROR)
                InternetGetLastResponseInfo(&ierr, tmp, &outbuf);
            Logger::Write(L"InternetReadFile(%s) failed %d (%d,%s)\n", name, err, ierr, tmp);
            break;
        }
        if (didread == 0)
            break;
        datalen += didread;
        if (datalen > bufferlen - INETBUFFERLEN)
        {
            bufferlen += INETBUFFERLEN;
            data = xrealloc(byte, data, bufferlen);
            if (!data)
            {
                datalen = 0;
                break;
            }
        }
    }
    if (datalen > 0)
    {
        zf = zfile_create(nullptr);
        if (zf)
        {
            zf->size = datalen;
            zf->data = data;
            data = nullptr;
        }
    }
end:
    if (i)
        InternetCloseHandle(i);
    free(data);
    return zf;
}
#endif

static struct zfile* zfile_fopenx2(const TCHAR* name, const TCHAR* mode, int mask, int index)
{
    struct zfile* f;
    TCHAR tmp[MAX_DPATH];

    #ifdef _WIN32
    if (isinternetfile(name))
        return zfile_fopen_internet(name, mode, mask);
    #endif
    f = zfile_fopen_x(name, mode, mask, index);
    if (f)
        return f;
    if (_tcslen(name) <= 2)
        return nullptr;
    if (name[1] != ':')
    {
        _tcscpy(tmp, start_path_data);
        _tcscat(tmp, name);
        f = zfile_fopen_x(tmp, mode, mask, index);
        if (f)
            return f;
    }
    #if 0
    name += 2;
    if (name[0] == '/' || name[0] == '\\')
        name++;
    for (;;)
    {
        _tcscpy(tmp, start_path_data);
        _tcscpy(tmp, name);
        f = zfile_fopen_x(tmp, mode, mask);
        if (f)
            return f;
        while (name[0])
        {
            name++;
            if (name[-1] == '/' || name[-1] == '\\')
                break;
        }
        if (name[0] == 0)
            break;
    }
    #endif
    return nullptr;
}

static struct zfile* zfile_fopenx(const TCHAR* name, const TCHAR* mode, int mask, int index)
{
    struct zfile* zf;
    // Logger::Write (L"zfile_fopen('%s','%s',%08x,%d)\n", name, mode, mask, index);
    zf = zfile_fopenx2(name, mode, mask, index);
    // Logger::Write (L"=%p\n", zf);
    return zf;
}

struct zfile* zfile_fopen(const TCHAR* name, const TCHAR* mode, int mask)
{
    return zfile_fopenx(name, mode, mask, 0);
}
struct zfile* zfile_fopen(const TCHAR* name, const TCHAR* mode)
{
    return zfile_fopenx(name, mode, 0, 0);
}
struct zfile* zfile_fopen(const TCHAR* name, const TCHAR* mode, int mask, int index)
{
    return zfile_fopenx(name, mode, mask, index);
}

struct zfile* zfile_dup(struct zfile* zf)
{
    struct zfile* nzf;
    if (!zf)
        return nullptr;
    if (zf->archiveparent)
        checkarchiveparent(zf);
    if (zf->userdata)
        return nullptr;
    if (!zf->data && zf->dataseek)
    {
        nzf = zfile_create(zf);
    }
    else if (zf->data)
    {
        nzf = zfile_create(zf);
        nzf->data = xmalloc(byte, zf->size);
        memcpy(nzf->data, zf->data, zf->size);
        nzf->size = zf->size;
        nzf->datasize = zf->datasize;
    }
    else
    {
        if (zf->zipname)
        {
            nzf = openzip(zf->name);
            if (nzf)
                return nzf;
        }
        FILE* ff = _tfopen(zf->name, zf->mode);
        if (!ff)
            return nullptr;
        nzf = zfile_create(zf);
        nzf->f = ff;
    }
    zfile_fseek(nzf, zf->seek, SEEK_SET);
    if (zf->name)
        nzf->name = _tcsdup(zf->name);
    if (nzf->zipname)
        nzf->zipname = _tcsdup(zf->zipname);
    nzf->zfdmask = zf->zfdmask;
    nzf->mode = _tcsdup(zf->mode);
    nzf->size = zf->size;
    return nzf;
}

int zfile_exists(const TCHAR* name)
{
    struct zfile* z;

    if (my_existsfile(name))
        return 1;
    z = zfile_fopen(name, L"rb", ZFD_NORMAL | ZFD_CHECKONLY);
    if (!z)
        return 0;
    zfile_fclose(z);
    return 1;
}

int zfile_iscompressed(struct zfile* z)
{
    return z->data ? 1 : 0;
}

struct zfile* zfile_fopen_empty(struct zfile* prev, const TCHAR* name, ulong64 size)
{
    struct zfile* l;
    l = zfile_create(prev);
    l->name = name ? _tcsdup(name) : L"";
    if (size)
    {
        l->data = xcalloc(byte, size);
        if (!l->data)
        {
            free(l);
            return nullptr;
        }
        l->size = size;
        l->datasize = size;
        l->allocsize = size;
    }
    else
    {
        l->data = xcalloc(byte, 1000);
        l->size = 0;
        l->allocsize = 1000;
    }
    return l;
}
struct zfile* zfile_fopen_empty(struct zfile* prev, const TCHAR* name)
{
    return zfile_fopen_empty(prev, name, 0);
}

struct zfile* zfile_fopen_parent(struct zfile* z, const TCHAR* name, ulong64 offset, ulong64 size)
{
    struct zfile* l;

    if (z == nullptr)
        return nullptr;
    l = zfile_create(z);
    if (name)
        l->name = _tcsdup(name);
    else if (z->name)
        l->name = _tcsdup(z->name);
    l->size = size;
    l->datasize = size;
    l->offset = offset;
    for (;;)
    {
        l->parent = z;
        l->useparent = 1;
        if (!z->parent)
            break;
        l->offset += z->offset;
        z = z->parent;
    }
    z->opencnt++;
    return l;
}

struct zfile* zfile_fopen_data(const TCHAR* name, ulong64 size, byte* data)
{
    struct zfile* l;

    l = zfile_create(nullptr);
    l->name = name ? _tcsdup(name) : L"";
    l->data = xmalloc(byte, size);
    l->size = size;
    l->datasize = size;
    memcpy(l->data, data, size);
    return l;
}

int zfile_truncate(struct zfile* z, long64 size)
{
    if (z->data)
    {
        if (z->size > size)
        {
            z->size = size;
            if (z->datasize > z->size)
                z->datasize = z->size;
            if (z->seek > z->size)
                z->seek = z->size;
            return 1;
        }
        return 0;
    }
    else
    {
        /* !!! */
        return 0;
    }
}

long64 zfile_size(struct zfile* z)
{
    return z->size;
}

long64 zfile_ftell(struct zfile* z)
{
    if (z->data || z->dataseek || z->parent)
        return z->seek;
    return _ftelli64(z->f);
}

long64 zfile_fseek(struct zfile* z, long64 offset, int mode)
{
    if (z->zfileseek)
        return z->zfileseek(z, offset, mode);
    if (z->data || z->dataseek || (z->parent && z->useparent))
    {
        int ret = 0;
        switch (mode)
        {
            case SEEK_SET:
                z->seek = offset;
                break;
            case SEEK_CUR:
                z->seek += offset;
                break;
            case SEEK_END:
                z->seek = z->size + offset;
                break;
        }
        if (z->seek < 0)
        {
            z->seek = 0;
            ret = 1;
        }
        if (z->seek > z->size)
        {
            z->seek = z->size;
            ret = 1;
        }
        return ret;
    }
    else
    {
        return _fseeki64(z->f, offset, mode);
    }
    // return 1;
}

size_t zfile_fread(void* b, size_t l1, size_t l2, struct zfile* z)
{
    if (z->zfileread)
        return z->zfileread(b, l1, l2, z);
    if (z->data)
    {
        if (z->datasize < z->size && z->seek + l1 * l2 > z->datasize)
        {
            if (z->archiveparent)
            {
                archive_unpackzfile(z);
                return zfile_fread(b, l1, l2, z);
            }

            return 0;
        }
        if (z->seek + l1 * l2 > z->size)
        {
            if (l1)
                l2 = (z->size - z->seek) / l1;
            else
                l2 = 0;
            if (l2 < 0)
                l2 = 0;
        }
        memcpy(b, z->data + z->offset + z->seek, l1 * l2);
        z->seek += l1 * l2;
        return l2;
    }
    if (z->parent && z->useparent)
    {
        size_t ret;
        long64 v;
        long64 size = z->size;
        v = z->seek;
        if (v + l1 * l2 > size)
        {
            if (l1)
                l2 = (size - v) / l1;
            else
                l2 = 0;
            if (l2 < 0)
                l2 = 0;
        }
        zfile_fseek(z->parent, z->seek + z->offset, SEEK_SET);
        v = z->seek;
        ret = zfile_fread(b, l1, l2, z->parent);
        z->seek = v + l1 * ret;
        return ret;
    }
    return fread(b, l1, l2, z->f);
}

size_t zfile_fwrite(void* b, size_t l1, size_t l2, struct zfile* z)
{
    if (z->archiveparent)
        return 0;
    if (z->zfilewrite)
        return z->zfilewrite(b, l1, l2, z);
    if (z->parent && z->useparent)
        return 0;
    if (z->data)
    {
        int off = z->seek + l1 * l2;
        if (z->allocsize == 0)
        {
            Logger::Write(L"zfile_fwrite(data,%s) but allocsize=0!\n", z->name);
            return 0;
        }
        if (off > z->allocsize)
        {
            if (z->allocsize < off)
                z->allocsize = off;
            z->allocsize += z->size / 2;
            if (z->allocsize < 10000)
                z->allocsize = 10000;
            z->data = xrealloc(byte, z->data, z->allocsize);
            z->datasize = z->size = off;
        }
        memcpy(z->data + z->seek, b, l1 * l2);
        z->seek += l1 * l2;
        if (z->seek > z->size)
            z->size = z->seek;
        if (z->size > z->datasize)
            z->datasize = z->size;
        return l2;
    }
    return fwrite(b, l1, l2, z->f);
}

size_t zfile_fputs(struct zfile* z, TCHAR* s)
{
    char* s2 = Unicode::ua(s);
    size_t t;
    t = zfile_fwrite(s2, strlen(s2), 1, z);
    free(s2);
    return t;
}

char* zfile_fgetsa(char* s, int size, struct zfile* z)
{
    checkarchiveparent(z);
    if (z->data)
    {
        char* os = s;
        int i;
        for (i = 0; i < size - 1; i++)
        {
            if (z->seek == z->size)
            {
                if (i == 0)
                    return nullptr;
                break;
            }
            *s = z->data[z->seek++];
            if (*s == '\n')
            {
                s++;
                break;
            }
            s++;
        }
        *s = 0;
        return os;
    }
    else
    {
        return fgets(s, size, z->f);
    }
}

TCHAR* zfile_fgets(TCHAR* s, int size, struct zfile* z)
{
    checkarchiveparent(z);
    if (z->data)
    {
        char s2[MAX_DPATH];
        char* p = s2;
        int i;
        for (i = 0; i < size - 1; i++)
        {
            if (z->seek == z->size)
            {
                if (i == 0)
                    return nullptr;
                break;
            }
            *p = z->data[z->seek++];
            if (*p == '\n')
            {
                p++;
                break;
            }
            p++;
        }
        *p = 0;
        if (size > strlen(s2) + 1)
            size = strlen(s2) + 1;
        Unicode::au_copy(s, size, s2);
        return s + size;
    }
    else
    {
        char s2[MAX_DPATH];
        char* s1;
        s1 = fgets(s2, size, z->f);
        if (!s1)
            return nullptr;
        if (size > strlen(s2) + 1)
            size = strlen(s2) + 1;
        Unicode::au_copy(s, size, s2);
        return s + size;
    }
}

int zfile_putc(int c, struct zfile* z)
{
    byte b = (byte)c;
    return zfile_fwrite(&b, 1, 1, z) ? 1 : -1;
}

int zfile_getc(struct zfile* z)
{
    checkarchiveparent(z);
    int out = -1;
    if (z->data)
    {
        if (z->seek < z->size)
        {
            out = z->data[z->seek++];
        }
    }
    else
    {
        out = fgetc(z->f);
    }
    return out;
}

int zfile_ferror(struct zfile* z)
{
    return 0;
}

byte* zfile_getdata(struct zfile* z, long64 offset, int len)
{
    long64 pos = zfile_ftell(z);
    byte* b;
    if (len < 0)
    {
        zfile_fseek(z, 0, SEEK_END);
        len = zfile_ftell(z);
        zfile_fseek(z, 0, SEEK_SET);
    }
    b = xmalloc(byte, len);
    zfile_fseek(z, offset, SEEK_SET);
    zfile_fread(b, len, 1, z);
    zfile_fseek(z, pos, SEEK_SET);
    return b;
}

int zfile_zuncompress(void* dst, int dstsize, struct zfile* src, int srcsize)
{
    z_stream zs;
    int v;
    byte inbuf[4096];
    int incnt;

    memset(&zs, 0, sizeof(zs));
    if (inflateInit_(&zs, ZLIB_VERSION, sizeof(z_stream)) != Z_OK)
        return 0;
    zs.next_out = (Bytef*)dst;
    zs.avail_out = dstsize;
    incnt = 0;
    v = Z_OK;
    while (v == Z_OK && zs.avail_out > 0)
    {
        if (zs.avail_in == 0)
        {
            int left = srcsize - incnt;
            if (left == 0)
                break;
            if (left > sizeof(inbuf))
                left = sizeof(inbuf);
            zs.next_in = inbuf;
            zs.avail_in = zfile_fread(inbuf, 1, left, src);
            incnt += left;
        }
        v = inflate(&zs, 0);
    }
    inflateEnd(&zs);
    return 0;
}

int zfile_zcompress(struct zfile* f, void* src, int size)
{
    int v;
    z_stream zs;
    byte outbuf[4096];

    memset(&zs, 0, sizeof(zs));
    if (deflateInit_(&zs, Z_DEFAULT_COMPRESSION, ZLIB_VERSION, sizeof(z_stream)) != Z_OK)
        return 0;
    zs.next_in = (Bytef*)src;
    zs.avail_in = size;
    v = Z_OK;
    while (v == Z_OK)
    {
        zs.next_out = outbuf;
        zs.avail_out = sizeof(outbuf);
        v = deflate(&zs, Z_NO_FLUSH | Z_FINISH);
        if (sizeof(outbuf) - zs.avail_out > 0)
            zfile_fwrite(outbuf, 1, sizeof(outbuf) - zs.avail_out, f);
    }
    deflateEnd(&zs);
    return zs.total_out;
}

TCHAR* zfile_getname(struct zfile* f)
{
    return f ? f->name : nullptr;
}

TCHAR* zfile_getfilename(struct zfile* f)
{
    int i;
    if (f->name == nullptr)
        return nullptr;
    for (i = _tcslen(f->name) - 1; i >= 0; i--)
    {
        if (f->name[i] == '\\' || f->name[i] == '/' || f->name[i] == ':')
        {
            i++;
            return &f->name[i];
        }
    }
    return f->name;
}

uint zfile_crc32(struct zfile* f)
{
    byte* p;
    int pos, size;
    uint crc;

    if (!f)
        return 0;
    if (f->data)
        return get_crc32(f->data, f->size);
    pos = zfile_ftell(f);
    zfile_fseek(f, 0, SEEK_END);
    size = zfile_ftell(f);
    p = xmalloc(byte, size);
    if (!p)
        return 0;
    memset(p, 0, size);
    zfile_fseek(f, 0, SEEK_SET);
    zfile_fread(p, 1, size, f);
    zfile_fseek(f, pos, SEEK_SET);
    crc = get_crc32(p, size);
    free(p);
    return crc;
}

static struct zvolume* zvolume_list;

static void recurparent(TCHAR* newpath, struct znode* zn, int recurse)
{
    if (zn->parent && (&zn->volume->root != zn->parent || zn->volume->parentz == nullptr))
    {
        if (&zn->volume->root == zn->parent && zn->volume->parentz == nullptr && !_tcscmp(zn->name, zn->parent->name))
            goto end;
        recurparent(newpath, zn->parent, recurse);
    }
    else
    {
        struct zvolume* zv = zn->volume;
        if (zv->parentz && recurse)
            recurparent(newpath, zv->parentz, recurse);
    }
end:
    if (newpath[0])
        _tcscat(newpath, FSDB_DIR_SEPARATOR_S);
    _tcscat(newpath, zn->name);
}

static struct znode* znode_alloc(struct znode* parent, const TCHAR* name)
{
    TCHAR fullpath[MAX_DPATH];
    TCHAR tmpname[MAX_DPATH];
    struct znode* zn = xcalloc(struct znode, 1);
    struct znode* zn2;

    _tcscpy(tmpname, name);
    zn2 = parent->child;
    while (zn2)
    {
        if (!_tcscmp(zn2->name, tmpname))
        {
            TCHAR* ext = _tcsrchr(tmpname, '.');
            if (ext && ext > tmpname + 2 && ext[-2] == '.')
            {
                ext[-1]++;
            }
            else if (ext)
            {
                memmove(ext + 2, ext, (_tcslen(ext) + 1) * sizeof(TCHAR));
                ext[0] = '.';
                ext[1] = '1';
            }
            else
            {
                int len = _tcslen(tmpname);
                tmpname[len] = '.';
                tmpname[len + 1] = '1';
                tmpname[len + 2] = 0;
            }
            zn2 = parent->child;
            continue;
        }
        zn2 = zn2->sibling;
    }

    fullpath[0] = 0;
    recurparent(fullpath, parent, FALSE);
    _tcscat(fullpath, FSDB_DIR_SEPARATOR_S);
    _tcscat(fullpath, tmpname);
    #if ZFILE_DEBUG_DEF
    Logger::Write(L"znode_alloc vol='%s' parent='%s' name='%s'\n", parent->volume->root.name, parent->name, name);
    #endif
    zn->fullname = _tcsdup(fullpath);
    zn->name = _tcsdup(tmpname);
    zn->volume = parent->volume;
    zn->volume->last->next = zn;
    zn->prev = zn->volume->last;
    zn->volume->last = zn;
    return zn;
}

static struct znode* znode_alloc_child(struct znode* parent, const TCHAR* name)
{
    struct znode* zn = znode_alloc(parent, name);

    if (!parent->child)
    {
        parent->child = zn;
    }
    else
    {
        struct znode* pn = parent->child;
        while (pn->sibling)
            pn = pn->sibling;
        pn->sibling = zn;
    }
    zn->parent = parent;
    return zn;
}
static struct znode* znode_alloc_sibling(struct znode* sibling, const TCHAR* name)
{
    struct znode* zn = znode_alloc(sibling->parent, name);

    if (!sibling->sibling)
    {
        sibling->sibling = zn;
    }
    else
    {
        struct znode* pn = sibling->sibling;
        while (pn->sibling)
            pn = pn->sibling;
        pn->sibling = zn;
    }
    zn->parent = sibling->parent;
    return zn;
}

static void zvolume_addtolist(struct zvolume* zv)
{
    if (!zv)
        return;
    if (!zvolume_list)
    {
        zvolume_list = zv;
    }
    else
    {
        struct zvolume* v = zvolume_list;
        while (v->next)
            v = v->next;
        v->next = zv;
    }
}

static struct zvolume* zvolume_alloc_2(const TCHAR* name, struct zfile* z, uint id, void* handle, const TCHAR* volname)
{
    struct zvolume* zv = xcalloc(struct zvolume, 1);
    struct znode* root;
    long64 pos;
    int i;

    root = &zv->root;
    zv->last = root;
    zv->archive = z;
    zv->handle = handle;
    zv->id = id;
    zv->blocks = 4;
    if (z)
        zv->zfdmask = z->zfdmask;
    root->volume = zv;
    root->type = ZNODE_DIR;
    i = 0;
    if (name[0] != '/' && name[0] != '\\' && _tcsncmp(name, L".\\", 2) != 0)
    {
        if (_tcschr(name, ':') == 0)
        {
            for (i = _tcslen(name) - 1; i > 0; i--)
            {
                if (name[i] == FSDB_DIR_SEPARATOR)
                {
                    i++;
                    break;
                }
            }
        }
    }
    root->name = _tcsdup(name + i);
    root->fullname = _tcsdup(name);
    #if ZFILE_DEBUG_DEF
    Logger::Write(L"created zvolume: '%s' (%s)\n", root->name, root->fullname);
    #endif
    if (volname)
        zv->volumename = _tcsdup(volname);
    if (z)
    {
        pos = zfile_ftell(z);
        zfile_fseek(z, 0, SEEK_END);
        zv->archivesize = zfile_ftell(z);
        zfile_fseek(z, pos, SEEK_SET);
    }
    return zv;
}
struct zvolume* zvolume_alloc(struct zfile* z, uint id, void* handle, const TCHAR* volumename)
{
    return zvolume_alloc_2(zfile_getname(z), z, id, handle, volumename);
}
struct zvolume* zvolume_alloc_nofile(const TCHAR* name, uint id, void* handle, const TCHAR* volumename)
{
    return zvolume_alloc_2(name, nullptr, id, handle, volumename);
}
struct zvolume* zvolume_alloc_empty(struct zvolume* prev, const TCHAR* name)
{
    struct zvolume* zv = zvolume_alloc_2(name, 0, 0, 0, nullptr);
    if (!zv)
        return nullptr;
    if (prev)
        zv->zfdmask = prev->zfdmask;
    return zv;
}

static struct zvolume* get_zvolume(const TCHAR* path)
{
    struct zvolume* zv = zvolume_list;
    while (zv)
    {
        TCHAR* s = zfile_getname(zv->archive);
        if (!s)
            s = zv->root.name;
        if (_tcslen(path) >= _tcslen(s) && !memcmp(path, s, _tcslen(s) * sizeof(TCHAR)))
            return zv;
        zv = zv->next;
    }
    return nullptr;
}

static struct zvolume* zfile_fopen_archive_ext(struct znode* parent, struct zfile* zf, int flags)
{
    struct zvolume* zv = nullptr;
    TCHAR* name = zfile_getname(zf);
    TCHAR* ext;
    byte header[7];

    if (!name)
        return nullptr;

    memset(header, 0, sizeof(header));
    zfile_fseek(zf, 0, SEEK_SET);
    zfile_fread(header, sizeof(header), 1, zf);
    zfile_fseek(zf, 0, SEEK_SET);

    ext = _tcsrchr(name, '.');
    if (ext != nullptr)
    {
        ext++;
        if (flags & ZFD_ARCHIVE)
        {
            if (_tcsicmp(ext, L"lha") == 0 || _tcsicmp(ext, L"lzh") == 0)
                zv = archive_directory_lha(zf);
            if (_tcsicmp(ext, L"zip") == 0)
                zv = archive_directory_zip(zf);
            if (_tcsicmp(ext, L"7z") == 0)
                zv = archive_directory_7z(zf);
            if (_tcsicmp(ext, L"lzx") == 0)
                zv = archive_directory_lzx(zf);
            if (_tcsicmp(ext, L"rar") == 0)
                zv = archive_directory_rar(zf);
            if (_tcsicmp(ext, L"tar") == 0)
                zv = archive_directory_tar(zf);
        }
        if (flags & ZFD_ADF)
        {
            if (_tcsicmp(ext, L"adf") == 0 && !memcmp(header, "DOS", 3))
                zv = archive_directory_adf(parent, zf);
        }
        if (flags & ZFD_HD)
        {
            if (_tcsicmp(ext, L"hdf") == 0)
            {
                if (!memcmp(header, "RDSK", 4))
                    zv = archive_directory_rdb(zf);
                else
                    zv = archive_directory_adf(parent, zf);
            }
        }
    }
    return zv;
}

static struct zvolume* zfile_fopen_archive_data(struct znode* parent, struct zfile* zf, int flags)
{
    struct zvolume* zv = nullptr;
    byte header[32];

    memset(header, 0, sizeof(header));
    zfile_fread(header, sizeof(header), 1, zf);
    zfile_fseek(zf, 0, SEEK_SET);
    if (flags & ZFD_ARCHIVE)
    {
        if (header[0] == 'P' && header[1] == 'K')
            zv = archive_directory_zip(zf);
        if (header[0] == 'R' && header[1] == 'a' && header[2] == 'r' && header[3] == '!')
            zv = archive_directory_rar(zf);
        if (header[0] == 'L' && header[1] == 'Z' && header[2] == 'X')
            zv = archive_directory_lzx(zf);
        if (header[2] == '-' && header[3] == 'l' && header[4] == 'h' && header[6] == '-')
            zv = archive_directory_lha(zf);
    }
    if (flags & ZFD_ADF)
    {
        if (header[0] == 'D' && header[1] == 'O' && header[2] == 'S' && (header[3] >= 0 && header[3] <= 7))
            zv = archive_directory_adf(parent, zf);
    }
    if (flags & ZFD_HD)
    {
        if (header[0] == 'R' && header[1] == 'D' && header[2] == 'S' && header[3] == 'K')
            zv = archive_directory_rdb(zf);
        if (isfat(header))
            zv = archive_directory_fat(zf);
    }
    return zv;
}

static struct znode* get_znode(struct zvolume* zv, const TCHAR* ppath, int);

static void zfile_fopen_archive_recurse2(struct zvolume* zv, struct znode* zn, int flags)
{
    struct zvolume* zvnew;
    struct znode* zndir;
    TCHAR tmp[MAX_DPATH];

    _stprintf(tmp, L"%s.DIR", zn->fullname + _tcslen(zv->root.name) + 1);
    zndir = get_znode(zv, tmp, TRUE);
    if (!zndir)
    {
        struct zarchive_info zai = { 0 };
        zvnew = zvolume_alloc_empty(zv, tmp);
        zvnew->parentz = zn;
        zai.name = tmp;
        zai.t = zn->mtime;
        zai.comment = zv->volumename;
        if (zn->flags < 0)
            zai.flags = zn->flags;
        zndir = zvolume_adddir_abs(zv, &zai);
        zndir->type = ZNODE_VDIR;
        zndir->vfile = zn;
        zndir->vchild = zvnew;
        zvnew->parent = zv;
        zndir->offset = zn->offset;
        zndir->offset2 = zn->offset2;
    }
}

static int zfile_fopen_archive_recurse(struct zvolume* zv, int flags)
{
    struct znode* zn;
    int i, added;

    added = 0;
    zn = zv->root.child;
    while (zn)
    {
        int done = 0;
        struct zfile* z;
        TCHAR* ext = _tcsrchr(zn->name, '.');
        if (ext && !zn->vchild && zn->type == ZNODE_FILE)
        {
            for (i = 0; !done && archive_extensions[i]; i++)
            {
                if (!_tcsicmp(ext + 1, archive_extensions[i]))
                {
                    zfile_fopen_archive_recurse2(zv, zn, flags);
                    done = 1;
                }
            }
        }
        if (!done)
        {
            z = archive_getzfile(zn, zv->method, 0);
            if (z && iszip(z))
                zfile_fopen_archive_recurse2(zv, zn, flags);
        }
        zn = zn->next;
    }
    return 0;
}

static struct zvolume* prepare_recursive_volume(struct zvolume* zv, const TCHAR* path, int flags)
{
    struct zfile* zf = nullptr;
    struct zvolume* zvnew = nullptr;
    int done = 0;

    #if ZFILE_DEBUG_DEF
    Logger::Write(L"unpacking '%s'\n", path);
    #endif
    zf = zfile_open_archive(path, 0);
    if (!zf)
        goto end;
    zvnew = zfile_fopen_archive_ext(zv->parentz, zf, flags);
    if (!zvnew && !(flags & ZFD_NORECURSE))
    {
        #if 1
        zvnew = archive_directory_plain(zf);
        if (zvnew)
        {
            zfile_fopen_archive_recurse(zvnew, flags);
            done = 1;
        }
        #else
        int rc;
        int index;
        struct zfile* zf2, * zf3;
        TCHAR oldname[MAX_DPATH];
        _tcscpy(oldname, zf->name);
        index = 0;
        for (;;)
        {
            zf3 = zfile_dup(zf);
            if (!zf3)
                break;
            zf2 = zuncompress(&zv->root, zf3, 0, ZFD_ALL, &rc, index);
            if (zf2)
            {
                zvnew = archive_directory_plain(zf2);
                if (zvnew)
                {
                    zvnew->parent = zv->parent;
                    zfile_fopen_archive_recurse(zvnew);
                    done = 1;
                }
            }
            else
            {
                zfile_fclose(zf3);
                if (rc <= 0)
                    break;
            }
            index++;
            break; // TODO
        }
        #endif
    }
    else if (zvnew)
    {
        zvnew->parent = zv->parent;
        zfile_fopen_archive_recurse(zvnew, flags);
        done = 1;
    }
    if (!done)
        goto end;
    zfile_fclose_archive(zv);
    return zvnew;
end:
    Logger::Write(L"unpack '%s' failed\n", path);
    zfile_fclose_archive(zvnew);
    zfile_fclose(zf);
    return nullptr;
}

static struct znode* get_znode(struct zvolume* zv, const TCHAR* ppath, int recurse)
{
    struct znode* zn;
    TCHAR path[MAX_DPATH], zpath[MAX_DPATH];

    if (!zv)
        return nullptr;
    _tcscpy(path, ppath);
    zn = &zv->root;
    while (zn)
    {
        zpath[0] = 0;
        recurparent(zpath, zn, recurse);
        if (zn->type == ZNODE_FILE)
        {
            if (!_tcsicmp(zpath, path))
                return zn;
        }
        else
        {
            int len = _tcslen(zpath);
            if (_tcslen(path) >= len && (path[len] == 0 || path[len] == FSDB_DIR_SEPARATOR) && !_tcsnicmp(zpath, path, len))
            {
                if (path[len] == 0)
                    return zn;
                if (zn->vchild)
                {
                    /* jump to separate tree, recursive archives */
                    struct zvolume* zvdeep = zn->vchild;
                    if (zvdeep->archive == nullptr)
                    {
                        TCHAR newpath[MAX_DPATH];
                        newpath[0] = 0;
                        recurparent(newpath, zn, recurse);
                        #if ZFILE_DEBUG_DEF
                        Logger::Write(L"'%s'\n", newpath);
                        #endif
                        zvdeep = prepare_recursive_volume(zvdeep, newpath, ZFD_ALL);
                        if (!zvdeep)
                        {
                            Logger::Write(L"failed to unpack '%s'\n", newpath);
                            return nullptr;
                        }
                        /* replace dummy empty volume with real volume */
                        zn->vchild = zvdeep;
                        zvdeep->parentz = zn;
                    }
                    zn = zvdeep->root.child;
                }
                else
                {
                    zn = zn->child;
                }
                continue;
            }
        }
        zn = zn->sibling;
    }
    return nullptr;
}

static void addvolumesize(struct zvolume* zv, long64 size)
{
    uint blocks = (size + 511) / 512;

    if (blocks == 0)
        blocks++;
    while (zv)
    {
        zv->blocks += blocks;
        zv->size += size;
        zv = zv->parent;
    }
}

struct znode* znode_adddir(struct znode* parent, const TCHAR* name, struct zarchive_info* zai)
{
    struct znode* zn;
    TCHAR path[MAX_DPATH];

    path[0] = 0;
    recurparent(path, parent, FALSE);
    _tcscat(path, FSDB_DIR_SEPARATOR_S);
    _tcscat(path, name);
    zn = get_znode(parent->volume, path, FALSE);
    if (zn)
        return zn;
    zn = znode_alloc_child(parent, name);
    zn->mtime = zai->t;
    zn->type = ZNODE_DIR;
    if (zai->comment)
        zn->comment = _tcsdup(zai->comment);
    if (zai->flags < 0)
        zn->flags = zai->flags;
    addvolumesize(parent->volume, 0);
    return zn;
}

struct znode* zvolume_adddir_abs(struct zvolume* zv, struct zarchive_info* zai)
{
    struct znode* zn2;
    TCHAR* path = _tcsdup(zai->name);
    TCHAR* p, * p2;
    int i;

    if (_tcslen(path) > 0)
    {
        /* remove possible trailing / or \ */
        TCHAR last;
        last = path[_tcslen(path) - 1];
        if (last == '/' || last == '\\')
            path[_tcslen(path) - 1] = 0;
    }
    zn2 = &zv->root;
    p = p2 = path;
    for (i = 0; path[i]; i++)
    {
        if (path[i] == '/' || path[i] == '\\')
        {
            path[i] = 0;
            zn2 = znode_adddir(zn2, p, zai);
            path[i] = FSDB_DIR_SEPARATOR;
            p = p2 = &path[i + 1];
        }
    }
    return znode_adddir(zn2, p, zai);
}

struct znode* zvolume_addfile_abs(struct zvolume* zv, struct zarchive_info* zai)
{
    struct znode* zn = nullptr, * zn2;
    int i;
    TCHAR* path = _tcsdup(zai->name);
    TCHAR* p, * p2;

    zn2 = &zv->root;
    p = p2 = path;
    for (i = 0; path[i]; i++)
    {
        if (path[i] == '/' || path[i] == '\\')
        {
            path[i] = 0;
            zn2 = znode_adddir(zn2, p, zai);
            path[i] = FSDB_DIR_SEPARATOR;
            p = p2 = &path[i + 1];
        }
    }
    if (p2)
    {
        zn = znode_alloc_child(zn2, p2);
        zn->size = zai->size;
        zn->type = ZNODE_FILE;
        zn->mtime = zai->t;
        if (zai->comment)
            zn->comment = _tcsdup(zai->comment);
        zn->flags = zai->flags;
        addvolumesize(zn->volume, zai->size);
    }
    free(path);
    return zn;
}

struct zvolume* zfile_fopen_directory(const TCHAR* dirname)
{
    struct zvolume* zv = nullptr;
    struct my_opendir_s* dir;
    TCHAR fname[MAX_DPATH];

    dir = my_opendir(dirname);
    if (!dir)
        return nullptr;
    zv = zvolume_alloc_nofile(dirname, ArchiveFormatDIR, nullptr, nullptr);
    while (my_readdir(dir, fname))
    {
        TCHAR fullname[MAX_DPATH];
        struct _stat64 statbuf;
        struct zarchive_info zai = { 0 };
        if (!_tcscmp(fname, L".") || !_tcscmp(fname, L".."))
            continue;
        _tcscpy(fullname, dirname);
        _tcscat(fullname, L"\\");
        _tcscat(fullname, fname);
        if (stat(fullname, &statbuf) == -1)
            continue;
        zai.name = fname;
        zai.size = statbuf.st_size;
        zai.t = statbuf.st_mtime;
        if (statbuf.st_mode & FILEFLAG_DIR)
        {
            zvolume_adddir_abs(zv, &zai);
        }
        else
        {
            struct znode* zn;
            zn = zvolume_addfile_abs(zv, &zai);
            // zfile_fopen_archive_recurse2 (zv, zn);
        }
    }
    my_closedir(dir);
    //     zfile_fopen_archive_recurse (zv);
    if (zv)
        zvolume_addtolist(zv);
    return zv;
}

struct zvolume* zfile_fopen_archive(const TCHAR* filename, int flags)
{
    struct zvolume* zv = nullptr;
    struct zfile* zf = zfile_fopen_nozip(filename, L"rb");

    if (!zf)
        return nullptr;
    zf->zfdmask = flags;
    zv = zfile_fopen_archive_ext(nullptr, zf, flags);
    if (!zv)
        zv = zfile_fopen_archive_data(nullptr, zf, flags);
    #if 0
    if (!zv)
    {
        struct zfile* zf2 = zuncompress(zf, 0, 0);
        if (zf2 != zf)
        {
            zf = zf2;
            zv = zfile_fopen_archive_ext(zf, flags);
            if (!zv)
                zv = zfile_fopen_archive_data(zf, flags);
        }
    }
    #endif
    /* pointless but who cares? */
    if (!zv && !(flags & ZFD_NORECURSE))
        zv = archive_directory_plain(zf);

    #if RECURSIVE_ARCHIVES
    if (zv && !(flags & ZFD_NORECURSE))
        zfile_fopen_archive_recurse(zv, flags);
    #endif

    if (zv)
        zvolume_addtolist(zv);
    else
        zfile_fclose(zf);

    return zv;
}
struct zvolume* zfile_fopen_archive(const TCHAR* filename)
{
    return zfile_fopen_archive(filename, ZFD_ALL);
}

struct zvolume* zfile_fopen_archive_root(const TCHAR* filename, int flags)
{
    TCHAR path[MAX_DPATH], * p1, * p2, * lastp;
    // struct zvolume* zv = nullptr;
    // int last = 0;
    int num, i;

    if (my_existsdir(filename))
        return zfile_fopen_directory(filename);

    num = 1;
    lastp = nullptr;
    for (;;)
    {
        _tcscpy(path, filename);
        p1 = p2 = path;
        for (i = 0; i < num; i++)
        {
            while (*p1 != FSDB_DIR_SEPARATOR && *p1 != 0)
                p1++;
            if (*p1 == 0 && p1 == lastp)
                return nullptr;
            if (i + 1 < num)
                p1++;
        }
        *p1 = 0;
        lastp = p1;
        if (my_existsfile(p2))
            return zfile_fopen_archive(p2, flags);
        num++;
    }

    #if 0
    while (!last)
    {
        while (*p1 != FSDB_DIR_SEPARATOR && *p1 != 0)
            p1++;
        if (*p1 == 0)
            last = 1;
        *p1 = 0;
        if (!zv)
        {
            zv = zfile_fopen_archive(p2);
            if (!zv)
                return nullptr;
        }
        else
        {
            struct znode* zn = get_znode(zv, p2);
            if (!zn)
                return nullptr;
        }
        p2 = p1 + 1;
    }
    return zv;
    #endif
}

void zfile_fclose_archive(struct zvolume* zv)
{
    struct znode* zn;
    struct zvolume* v;

    if (!zv)
        return;
    zn = &zv->root;
    while (zn)
    {
        struct znode* zn2 = zn->next;
        if (zn->vchild)
            zfile_fclose_archive(zn->vchild);
        free(zn->comment);
        free(zn->fullname);
        free(zn->name);
        zfile_fclose(zn->f);
        memset(zn, 0, sizeof(struct znode));
        if (zn != &zv->root)
            free(zn);
        zn = zn2;
    }
    archive_access_close(zv->handle, zv->id);
    if (zvolume_list == zv)
    {
        zvolume_list = zvolume_list->next;
    }
    else
    {
        v = zvolume_list;
        while (v)
        {
            if (v->next == zv)
            {
                v->next = zv->next;
                break;
            }
            v = v->next;
        }
    }
    free(zv);
}

struct zdirectory
{
    TCHAR* parentpath;
    struct znode* first;
    struct znode* n;
    bool doclose;
    struct zvolume* zv;
    int cnt;
    int offset;
    TCHAR** filenames;
};

struct zdirectory* zfile_opendir_archive(const TCHAR* path, int flags)
{
    struct zvolume* zv = get_zvolume(path);
    bool created = false;
    if (zv == nullptr)
    {
        zv = zfile_fopen_archive(path, flags);
        created = true;
    }
    struct znode* zn = get_znode(zv, path, TRUE);
    struct zdirectory* zd;
    if (!zn || (!zn->child && !zn->vchild))
    {
        if (created)
            zfile_fclose_archive(zv);
        return nullptr;
    }
    zd = xcalloc(struct zdirectory, 1);
    if (created)
        zd->zv = zv;
    if (zn->child)
    {
        zd->n = zn->child;
    }
    else
    {
        if (zn->vchild->archive == nullptr)
        {
            struct zvolume* zvnew = prepare_recursive_volume(zn->vchild, path, flags);
            if (zvnew)
            {
                zn->vchild = zvnew;
                zvnew->parentz = zn;
            }
        }
        zd->n = zn->vchild->root.next;
    }
    zd->parentpath = _tcsdup(path);
    zd->first = zd->n;
    return zd;
}
struct zdirectory* zfile_opendir_archive(const TCHAR* path)
{
    return zfile_opendir_archive(path, ZFD_ALL | ZFD_NORECURSE);
}
void zfile_closedir_archive(struct zdirectory* zd)
{
    if (!zd)
        return;
    zfile_fclose_archive(zd->zv);
    free(zd->parentpath);
    free(zd->filenames);
    free(zd);
}
int zfile_readdir_archive(struct zdirectory* zd, TCHAR* out, bool fullpath)
{
    if (out)
        out[0] = 0;
    if (!zd->n || (zd->filenames != nullptr && zd->offset >= zd->cnt))
        return 0;
    if (zd->filenames == nullptr)
    {
        struct znode* n = zd->first;
        int cnt = 0 /*, len = 0*/;
        while (n)
        {
            cnt++;
            n = n->sibling;
        }
        n = zd->first;
        byte* buf = xmalloc(byte, cnt * sizeof(TCHAR*));
        zd->filenames = (TCHAR**)buf;
        buf += cnt * sizeof(TCHAR*);
        for (int i = 0; i < cnt; i++)
        {
            zd->filenames[i] = n->name;
            n = n->sibling;
        }
        for (int i = 0; i < cnt; i++)
        {
            for (int j = i + 1; j < cnt; j++)
            {
                if (_tcscmp(zd->filenames[i], zd->filenames[j]) > 0)
                {
                    TCHAR* tmp = zd->filenames[i];
                    zd->filenames[i] = zd->filenames[j];
                    zd->filenames[j] = tmp;
                }
            }
        }
        zd->cnt = cnt;
    }
    if (out == nullptr)
        return zd->cnt;
    if (fullpath)
    {
        _tcscpy(out, zd->parentpath);
        _tcscat(out, L"\\");
    }
    _tcscat(out, zd->filenames[zd->offset]);
    zd->offset++;
    return 1;
}
int zfile_readdir_archive(struct zdirectory* zd, TCHAR* out)
{
    return zfile_readdir_archive(zd, out, false);
}
void zfile_resetdir_archive(struct zdirectory* zd)
{
    zd->offset = 0;
    zd->n = zd->first;
}

int zfile_fill_file_attrs_archive(const TCHAR* path, int* isdir, int* flags, TCHAR** comment)
{
    struct zvolume* zv = get_zvolume(path);
    struct znode* zn = get_znode(zv, path, TRUE);

    *isdir = 0;
    *flags = 0;
    if (comment)
        *comment = 0;
    if (!zn)
        return 0;
    if (zn->type == ZNODE_DIR)
        *isdir = 1;
    else if (zn->type == ZNODE_VDIR)
        *isdir = -1;
    *flags = zn->flags;
    if (zn->comment && comment)
        *comment = _tcsdup(zn->comment);
    return 1;
}

int zfile_fs_usage_archive(const TCHAR* path, const TCHAR* disk, struct fs_usage* fsp)
{
    struct zvolume* zv = get_zvolume(path);

    if (!zv)
        return -1;
    fsp->fsu_blocks = zv->blocks;
    fsp->fsu_bavail = 0;
    return 0;
}

int zfile_stat_archive(const TCHAR* path, struct _stat64* s)
{
    struct zvolume* zv = get_zvolume(path);
    struct znode* zn = get_znode(zv, path, TRUE);

    memset(s, 0, sizeof(struct _stat64));
    if (!zn)
        return 0;
    s->st_mode = zn->type == ZNODE_FILE ? 0 : FILEFLAG_DIR;
    s->st_size = zn->size;
    s->st_mtime = zn->mtime;
    return 1;
}

long64 zfile_lseek_archive(struct zfile* d, long64 offset, int whence)
{
    if (zfile_fseek(d, offset, whence))
        return -1;
    return zfile_ftell(d);
}

uint zfile_read_archive(struct zfile* d, void* b, uint size)
{
    return zfile_fread(b, 1, size, d);
}

void zfile_close_archive(struct zfile* d)
{
    /* do nothing, keep file cached */
}

struct zfile* zfile_open_archive(const TCHAR* path, int flags)
{
    struct zvolume* zv = get_zvolume(path);
    struct znode* zn = get_znode(zv, path, TRUE);
    struct zfile* z;

    if (!zn)
        return 0;
    if (zn->f)
    {
        zfile_fseek(zn->f, 0, SEEK_SET);
        return zn->f;
    }
    if (zn->vfile)
        zn = zn->vfile;
    z = archive_getzfile(zn, zn->volume->id, 0);
    if (z)
        zfile_fseek(z, 0, SEEK_SET);
    zn->f = z;
    return zn->f;
}

int zfile_exists_archive(const TCHAR* path, const TCHAR* rel)
{
    TCHAR tmp[MAX_DPATH];
    struct zvolume* zv;
    struct znode* zn;

    _stprintf(tmp, L"%s%c%s", path, FSDB_DIR_SEPARATOR, rel);
    zv = get_zvolume(tmp);
    zn = get_znode(zv, tmp, TRUE);
    return zn ? 1 : 0;
}

// int zfile_convertimage(const TCHAR* src, const TCHAR* dst)
// {
//     struct zfile* s, * d;
//     int ret = 0;
//
//     s = zfile_fopen(src, L"rb", ZFD_NORMAL);
//     if (s)
//     {
//         byte* b;
//         int size;
//         zfile_fseek(s, 0, SEEK_END);
//         size = zfile_ftell(s);
//         zfile_fseek(s, 0, SEEK_SET);
//         b = xcalloc(byte, size);
//         if (b)
//         {
//             if (zfile_fread(b, size, 1, s) == 1)
//             {
//                 d = zfile_fopen(dst, L"wb", 0);
//                 if (d)
//                 {
//                     if (zfile_fwrite(b, size, 1, d) == 1)
//                         ret = 1;
//                     zfile_fclose(d);
//                 }
//             }
//             free(b);
//         }
//         zfile_fclose(s);
//     }
//     return ret;
// }

static TCHAR* zerror;

#define WRITE_LOG_BUF_SIZE 4096
void zfile_seterror(const TCHAR* format, ...)
{
    int count;
    if (!zerror)
    {
        TCHAR buffer[WRITE_LOG_BUF_SIZE];
        va_list parms;
        va_start(parms, format);
        count = _vsntprintf(buffer, WRITE_LOG_BUF_SIZE - 1, format, parms);
        zerror = _tcsdup(buffer);
        va_end(parms);
    }
}

TCHAR* zfile_geterror()
{
    return zerror;
}